1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-03 07:03:00 +00:00

Compare commits

...

31 Commits

Author SHA1 Message Date
Jonathan Coates
fc834cd97f Update to 1.20.4 2024-01-31 20:55:14 +00:00
Jonathan Coates
f26e443e81 Bump CC:T to 1.109.5 2024-01-31 20:53:28 +00:00
Jonathan Coates
033378333f Standardise how we discard "char" events
One common issue we get when a program exits after handling a "key"
event is that it leaves the "char" event on the queue. This means that
the shell (or whatever program we switch in to) then receives the "char"
event, often displaying it to the screen.

Previously we've got around this by doing sleep(0) before exiting the
program. However, we also see this problem in edit's run handler script,
and I'm less comfortable doing the same hack there.

This adds a new internal discard_char function, which will either
wait one tick or return when seeing a char/key_up event.

Fixes #1705
2024-01-31 20:49:43 +00:00
Jonathan Coates
ebeaa757a9 Change how we put test libraries on the class path
- Mark our core test-fixtures jar as part of the "cctest", rather than
   a separate library. I'm fairly sure this was actually using the
   classpath version of CC rather than the legacyClasspath version!

 - Add a new "testMinecraftLibrary" configuration, instead of trying to
   infer it from the classpath. We have to jump through some hoops to
   avoid having multiple versions of a library on the classpath at once,
   but it's not too bad.

I'm working on a patch to bsl which might allow us to kill of
legacyClasspath instead. Please, anything is better than this.
2024-01-31 19:49:36 +00:00
Jonathan Coates
57b1a65db3 Fix using a tab instead of space
Annoying that pre-commit didn't catch this!
2024-01-30 22:01:57 +00:00
Jonathan Coates
27c72a4571 Use client-side commands for opening computer folders
Forge doesn't run client-side commands from sendUnsignedCommand, so we
still require a mixin there.

We do need to change the command name, as Fabric doesn't properly merge
the two command trees.
2024-01-30 22:00:36 +00:00
Jonathan Coates
f284328656 Regenerate Gradle wrapper 2024-01-29 22:14:48 +00:00
Jonathan Coates
6b83c63991 Switch to our own Gradle plugin for vanilla Minecraft
I didn't make a new years resolution to stop writing build tooling, but
maybe I should have.

This replaces our use of VanillaGradle with a new project,
VanillaExtract. This offers a couple of useful features for multi-loader
dev, including Parchment and Unpick support, both of which we now use in
CC:T.
2024-01-29 20:59:16 +00:00
Jonathan Coates
b27526bd21 Bump CC:T to 1.109.4 2024-01-27 10:26:56 +00:00
Jonathan Coates
cb25f6c08a Update Cobalt to 0.9.0
- Debug hooks are now correctly called for every function.
 - Fix several minor inconsistencies with debug.getinfo.
 - Fix Lua tables being sized incorrectly when created from varargs.
2024-01-27 10:04:34 +00:00
Jonathan Coates
d38b1d04e7 Update Gradle to 8.5
- Update FG to 6.0.20 - no major changes, but required for the Gradle
   update.
 - Update Loom to 1.5.x - this adds Vineflower support by default, so we
   can remove loom-vineflower.
2024-01-27 09:28:13 +00:00
Jonathan Coates
9ccee75a99 Fix the docs for ReadHandle.read's "count"
This was copied over from the old binary handle, and so states we
always return a single number if no count is given. This is only the
case when the file is opened in binary mode.
2024-01-23 22:39:49 +00:00
Jonathan Coates
359c8d6652 Reformat JSON by wrapping CachedOutput
Rather than mixing-in to CachedOutput, we just wrap our DataProviders to
use a custom CachedOutput which reformats the JSON before writing. This
allows us to drop mixins for common+non-client code.
2024-01-21 17:50:59 +00:00
Jonathan Coates
1788afacfc Remove note about disabling websocket limits
I suspect this was copied from the file limit, which can be turned off
by setting to 0.

Fixes #1691
2024-01-21 16:32:07 +00:00
Jonathan Coates
f695f22d8a Atomic update of disk drive item stacks
Disk drives have had a long-standing issue with mutating their contents
on the computer thread, potentially leading to all sorts of odd bugs.

We tried to fix this by moving setDiskLabel and the mounting code to run
on the main thread. Unfortunately, this means there is a slight delay to
mounts being attached, breaking disk startup.

This commit implements an alternative solution - we now do mounting on
the computer thread again. If the disk's stack is modified, we update it
in the peripheral-facing item, but not the actual inventory. The next
time the disk drive is ticked, we then sync the two items.

This does mean that there is a fraction of a tick where the two will be
out-of-sync. This isn't ideal - it would potentially be possible to
cycle through disk ids - but I don't really think that's avoidable
without significantly complicating the IMedia API.

Fixes #1649, fixes #1686.
2024-01-20 18:46:43 +00:00
Jonathan Coates
bc03090ca4 Merge pull request #1684 from cc-tweaked/hotfix/turtle-modellers-redo
Rewrite turtle upgrade modeller registration API
2024-01-17 19:45:48 +00:00
Jonathan Coates
a617d0d566 Rewrite turtle upgrade modeller registration API
Originally we exposed a single registerTurtleUpgradeModellermethod which
could be called from both Fabric (during a mod's client init) and Forge
(during FMLClientSetupEvent).

This was fine until we allowed upgrades to specify model dependencies,
which would then automatically loaded, as this means model loading now
depends on upgrade modellers being loaded. Unknown to me, this is not
guaranteed to be the case on Forge - mod setup happens at the same time
as resource reloading!

Unfortunately there's not really a salvageable way of fixing this with
the current API. Forge now uses a registration event-based system,
meaning we can guarantee all modellers are loaded before models are
baked.
2024-01-16 23:00:49 +00:00
Jonathan Coates
36599b321e Backport small changes from the 1.20.4 branch
- Add support for version overrides/exclusions in our dependency check.
   Sometimes mod loaders use different versions to vanilla, and we need
   some way to handle that.

 - Rescan wired network connections on the tick after invalidation,
   rather than when invalidated.

 - Convert some constant lambdas to static method references. Lambdas
   don't allocate if they don't capture variables, so this has the same
   performance and is a little less ugly.

 - Small code-style/formatting changes.
2024-01-16 21:42:25 +00:00
Jonathan Coates
1d6e3f4fc0 Change ComponentLookup to use ServerLevel
Makes this more consistent with the rest of the peripheral code, and our
changes in 1.20.4.
2024-01-15 08:28:59 +00:00
Jonathan Coates
30dc4cb38c Simplify our networking multi-platform code
Historically we used Forge's SimpleChannel methods (and
PacketDistributor) to send the packets to the client. However, we don't
need to do that - it is sufficient to convert it to a vanilla packet,
and send the packet ourselves.

Given we need to do this on Fabric, it makes sense to do this on Forge
as well. This allows us to unify (and thus simplify) a lot of how packet
sending works.

At the same time, we also remove the handling of speaker audio during
decoding. We originally did this to avoid the additional copy of audio
data. However, this doesn't work on 1.20.4 (as packets aren't
encoded/decoded on singleplayer), so it makes sense to do this
Correctly(TM).

This also allows us to get rid of ClientNetworkContext.get(). We do
still need to service load this class (as Forge's networking isn't split
up in the same way Fabric's is), but we'll be able to drop that in
1.20.4.

Finally, we move the record playing code from ClientNetworkContext to
ClientPlatformHelper. This means the network context no longer needs to
be platform-specific!
2024-01-14 22:53:36 +00:00
Jonathan Coates
be4512d1c3 Construct ComponentAccesses with the BE
After embarrassing, let's do some proper work.

Rather than passing the level and position each time we call
ComponentAccess.get(), we now pass them at construction time (in the
form of the BE). This makes the consuming code a little cleaner, and is
required for the NeoForge changes in 1.20.4.
2024-01-14 17:46:37 +00:00
Jonathan Coates
e6ee292850 Fix incorrect "Fix incorrect "incorrect incorrect"" 2024-01-14 16:27:55 +00:00
Jonathan Coates
9d36f72bad Fix incorrect "incorrect incorrect" 2024-01-14 16:12:52 +00:00
Jonathan Coates
b5923c4462 Flesh out the printer documentation slightly 2024-01-14 12:25:04 +00:00
Jonathan Coates
4d1e689719 Fix endPage() not updating the printer block state
This meant that we didn't show the bottom slot was full until other
items were moved in the inventory.
2024-01-14 12:23:55 +00:00
Marcus
9d4af07568 fix: breaking_changes flattening link incorrect (#1679) 2024-01-10 22:18:22 +00:00
lonevox
89294f4a22 Fix incorrect Lua list indexes in NBT tags (#1678) 2024-01-10 19:16:15 +00:00
Jonathan Coates
133b51b092 Don't warn when allocating 0 bytes
I was able to reproduce this by starting two computers, and then warming
up the JIT by running:

  while true do os.queueEvent("x") os.pullEvent("x") end

and then running the following on one computer, while typing on the
other:

  while true do end

I'm not quite sure why this happens. It's possible that once the JIT is
warm, we can resume computers without actually allocating anything,
though I'm a little unconvinced.

Fixes #1672
2024-01-08 21:52:30 +00:00
Jonathan Coates
272010e945 Require Minecraft 1.20.1
Closes #1671
2024-01-08 21:33:55 +00:00
Jonathan Coates
e0889c613a Mark "check valid item" test as required
This has passed for years now, no reason for it to be optional.
2024-01-07 13:35:38 +00:00
Jonathan Coates
f115d43d07 Fix some dependencies not appearing in the POM
Again! This time it was just the night-config ones.
2024-01-03 21:05:03 +00:00
291 changed files with 4589 additions and 4180 deletions

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
/logs
/build
/projects/*/logs
/projects/fabric/fabricloader.log
/projects/*/build
/buildSrc/build
/out

View File

@@ -51,9 +51,8 @@ dependencies {
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
// Forge Gradle
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion"))
runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion"))
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion")
runtimeOnly("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion")
// Fabric Loom
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$cctVersion")
@@ -75,8 +74,8 @@ minecraft {
```
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
subject to change at any point. If you depend on functionality outside the API, file an issue, and we can look into
exposing more features.
subject to change at any point. If you depend on functionality outside the API (or need to mixin to CC:T), please file
an issue to let me know!
We bundle the API sources with the jar, so documentation should be easily viewable within your editor. Alternatively,
the generated documentation [can be browsed online](https://tweaked.cc/javadoc/).

View File

@@ -14,25 +14,14 @@ repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.minecraftforge.net") {
name = "Forge"
maven("https://maven.neoforged.net/releases") {
name = "NeoForge"
content {
includeGroup("net.minecraftforge")
includeGroup("net.minecraftforge.gradle")
}
}
maven("https://maven.parchmentmc.org") {
name = "Librarian"
content {
includeGroupByRegex("^org\\.parchmentmc.*")
}
}
maven("https://repo.spongepowered.org/repository/maven-public/") {
name = "Sponge"
content {
includeGroup("org.spongepowered")
includeGroup("net.neoforged")
includeGroup("net.neoforged.gradle")
includeModule("codechicken", "DiffPatch")
includeModule("net.covers1624", "Quack")
}
}
@@ -42,6 +31,13 @@ repositories {
includeGroup("net.fabricmc")
}
}
maven("https://squiddev.cc/maven") {
name = "SquidDev"
content {
includeGroup("cc.tweaked.vanilla-extract")
}
}
}
dependencies {
@@ -51,12 +47,10 @@ dependencies {
implementation(libs.curseForgeGradle)
implementation(libs.fabric.loom)
implementation(libs.forgeGradle)
implementation(libs.ideaExt)
implementation(libs.librarian)
implementation(libs.minotaur)
implementation(libs.vanillaGradle)
implementation(libs.vineflower)
implementation(libs.neoGradle.userdev)
implementation(libs.vanillaExtract)
}
gradlePlugin {

View File

@@ -12,7 +12,6 @@ import cc.tweaked.gradle.MinecraftConfigurations
plugins {
`java-library`
id("fabric-loom")
id("io.github.juuxel.loom-vineflower")
id("cc-tweaked.java-convention")
}

View File

@@ -10,10 +10,8 @@ import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("net.minecraftforge.gradle")
// We must apply java-convention after Forge, as we need the fg extension to be present.
id("cc-tweaked.java-convention")
id("org.parchmentmc.librarian.forgegradle")
id("net.neoforged.gradle.userdev")
}
plugins.apply(CCTweakedPlugin::class.java)
@@ -21,10 +19,20 @@ plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
modIdentifier("computercraft")
}
accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
subsystems {
parchment {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
minecraftVersion = libs.findVersion("parchmentMc").get().toString()
mappingsVersion = libs.findVersion("parchment").get().toString()
}
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
implementation("net.neoforged:neoforge:${libs.findVersion("neoForge").get()}")
}
MinecraftConfigurations.setup(project)
@@ -32,13 +40,3 @@ MinecraftConfigurations.setup(project)
extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = "forge")
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
"minecraft"("net.minecraftforge:forge:$mcVersion-${libs.findVersion("forge").get()}")
}
tasks.configureEach {
// genIntellijRuns isn't registered until much later, so we need this silly hijinks.
if (name == "genIntellijRuns") doLast { IdeaRunConfigurations(project).patch() }
}

View File

@@ -40,21 +40,10 @@ repositories {
val mainMaven = maven("https://squiddev.cc/maven") {
name = "SquidDev"
content {
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
includeModule("org.spongepowered", "mixin")
}
}
exclusiveContent {
forRepositories(mainMaven)
// Include the ForgeGradle repository if present. This requires that ForgeGradle is already present, which we
// enforce in our Forge overlay.
val fg =
project.extensions.findByType(net.minecraftforge.gradle.userdev.DependencyManagementExtension::class.java)
if (fg != null) forRepositories(fg.repository)
filter {
includeGroup("cc.tweaked")
// Things we mirror

View File

@@ -10,25 +10,31 @@ import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("cc-tweaked.java-convention")
id("org.spongepowered.gradle.vanilla")
id("cc.tweaked.vanilla-extract")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
minecraft {
version(mcVersion)
mappings {
parchment(libs.findVersion("parchmentMc").get().toString(), libs.findVersion("parchment").get().toString())
}
unpick(libs.findLibrary("yarn").get())
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
// Depend on error prone annotations to silence a lot of compile warnings.
compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
compileOnly(libs.findLibrary("errorProne.annotations").get())
}
MinecraftConfigurations.setup(project)
MinecraftConfigurations.setupBasic(project)
extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = null)

View File

@@ -255,7 +255,7 @@ abstract class CCTweakedExtension(
}
/**
* Exclude a dependency from being publisehd in Maven.
* Exclude a dependency from being published in Maven.
*/
fun exclude(dep: Dependency) {
excludedDeps.add(dep)

View File

@@ -7,12 +7,15 @@ package cc.tweaked.gradle
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ModuleComponentSelector
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.DependencyResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.language.base.plugins.LifecycleBasePlugin
@@ -21,9 +24,25 @@ abstract class DependencyCheck : DefaultTask() {
@get:Input
abstract val configuration: ListProperty<Configuration>
/**
* A mapping of module coordinates (`group:module`) to versions, overriding the requested version.
*/
@get:Input
abstract val overrides: MapProperty<String, String>
init {
description = "Check :core's dependencies are consistent with Minecraft's."
group = LifecycleBasePlugin.VERIFICATION_GROUP
configuration.finalizeValueOnRead()
overrides.finalizeValueOnRead()
}
/**
* Override a module with a different version.
*/
fun override(module: Provider<MinimalExternalModuleDependency>, version: String) {
overrides.putAll(project.provider { mutableMapOf(module.get().module.toString() to version) })
}
@TaskAction
@@ -60,7 +79,8 @@ abstract class DependencyCheck : DefaultTask() {
) {
// If the version is different between the requested and selected version, report an error.
val selected = dependency.selected.moduleVersion!!.version
if (requested.version != selected) {
val requestedVersion = overrides.get()["${requested.group}:${requested.module}"] ?: requested.version
if (requestedVersion != selected) {
logger.error("Requested dependency {} (via {}) but got version {}", requested, from, selected)
return false
}

View File

@@ -4,23 +4,59 @@
package cc.tweaked.gradle
import net.minecraftforge.gradle.common.util.RunConfig
import net.minecraftforge.gradle.common.util.runs.setRunConfigInternal
import net.neoforged.gradle.common.runs.run.RunImpl
import net.neoforged.gradle.common.runs.tasks.RunExec
import net.neoforged.gradle.dsl.common.extensions.RunnableSourceSet
import net.neoforged.gradle.dsl.common.runs.run.Run
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.SourceSet
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.findByType
import java.nio.file.Files
/**
* Set [JavaExec] task to run a given [RunConfig].
*
* See also [RunExec].
*/
fun JavaExec.setRunConfig(config: RunConfig) {
dependsOn("prepareRuns")
setRunConfigInternal(project, this, config)
doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
fun JavaExec.setRunConfig(config: Run) {
mainClass.set(config.mainClass)
workingDir = config.workingDirectory.get().asFile
argumentProviders.add { config.programArguments.get() }
jvmArgumentProviders.add { config.jvmArguments.get() }
environment(config.environmentVariables.get())
systemProperties(config.systemProperties.get())
config.modSources.get().forEach { classpath(it.runtimeClasspath) }
classpath(config.classpath)
classpath(config.dependencies.get().configuration)
(config as RunImpl).taskDependencies.forEach { dependsOn(it) }
javaLauncher.set(
project.extensions.getByType(JavaToolchainService::class.java)
.launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain),
)
doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
}
/**
* Add a new [Run.modSource] with a specific mod id.
*/
fun Run.modSourceAs(sourceSet: SourceSet, mod: String) {
// NeoGradle requires a RunnableSourceSet to be present, so we inject it into other project's source sets.
val runnable = sourceSet.extensions.findByType<RunnableSourceSet>() ?: run {
val extension = sourceSet.extensions.create<RunnableSourceSet>(RunnableSourceSet.NAME, project)
extension.modIdentifier = mod
extension.modIdentifier.finalizeValueOnRead()
extension
}
if (runnable.modIdentifier.get() != mod) throw IllegalArgumentException("Multiple mod identifiers")
modSource(sourceSet)
}

View File

@@ -4,23 +4,17 @@
package cc.tweaked.gradle
import cc.tweaked.vanillaextract.configurations.Capabilities
import cc.tweaked.vanillaextract.configurations.MinecraftSetup
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.attributes.Bundling
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.Usage
import org.gradle.api.attributes.java.TargetJvmVersion
import org.gradle.api.capabilities.Capability
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.named
/**
* This sets up a separate client-only source set, and extends that and the main/common source set with additional
@@ -59,31 +53,13 @@ class MinecraftConfigurations private constructor(private val project: Project)
}
configurations.named(client.implementationConfigurationName) { extendsFrom(clientApi) }
/*
Now add outgoing variants for the main and common source sets that we can consume downstream. This is possibly
the worst way to do things, but unfortunately the alternatives don't actually work very well:
- Just using source set outputs: This means dependencies don't propagate, which means when :fabric depends
on :fabric-api, we don't inherit the fake :common-api in IDEA.
- Having separate common/main jars: Nice in principle, but unfortunately Forge needs a separate deobf jar
task (as the original jar is obfuscated), and IDEA is not able to map its output back to a source set.
This works for now, but is incredibly brittle. It's part of the reason we can't use testFixtures inside our
MC projects, as that adds a project(self) -> test dependency, which would pull in the jar instead.
Note we register a fake client jar here. It's not actually needed, but is there to make sure IDEA has
a way to tell that client classes are needed at runtime.
I'm so sorry, deeply aware how cursed this is.
*/
setupOutgoing(main, "CommonOnly")
project.tasks.register(client.jarTaskName, Jar::class.java) {
description = "An empty jar standing in for the client classes."
group = BasePlugin.BUILD_GROUP
archiveClassifier.set("client")
}
setupOutgoing(client)
MinecraftSetup(project).setupOutgoingConfigurations()
// Reset the client classpath (Loom configures it slightly differently to this) and add a main -> client
// dependency. Here we /can/ use source set outputs as we add transitive deps by patching the classpath. Nasty,
@@ -106,6 +82,12 @@ class MinecraftConfigurations private constructor(private val project: Project)
project.tasks.named("jar", Jar::class.java) { from(client.output) }
project.tasks.named("sourcesJar", Jar::class.java) { from(client.allSource) }
setupBasic()
}
private fun setupBasic() {
val client = sourceSets["client"]
project.extensions.configure(CCTweakedExtension::class.java) {
sourceDirectories.add(SourceSetReference.internal(client))
}
@@ -120,83 +102,19 @@ class MinecraftConfigurations private constructor(private val project: Project)
project.tasks.named("check") { dependsOn(checkDependencyConsistency) }
}
private fun setupOutgoing(sourceSet: SourceSet, suffix: String = "") {
setupOutgoing("${sourceSet.apiElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_API)) {
description = "API elements for ${sourceSet.name}"
extendsFrom(configurations[sourceSet.apiConfigurationName])
}
setupOutgoing("${sourceSet.runtimeElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_RUNTIME)) {
description = "Runtime elements for ${sourceSet.name}"
extendsFrom(configurations[sourceSet.implementationConfigurationName], configurations[sourceSet.runtimeOnlyConfigurationName])
}
}
/**
* Set up an outgoing configuration for a specific source set. We set an additional "main" or "client" capability
* (depending on the source set name) which allows downstream projects to consume them separately (see
* [DependencyHandler.commonClasses] and [DependencyHandler.clientClasses]).
*/
private fun setupOutgoing(name: String, sourceSet: SourceSet, usage: Usage, configure: Configuration.() -> Unit) {
configurations.register(name) {
isVisible = false
isCanBeConsumed = true
isCanBeResolved = false
configure(this)
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
attribute(Usage.USAGE_ATTRIBUTE, usage)
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
attributeProvider(
TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,
java.toolchain.languageVersion.map { it.asInt() },
)
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
}
outgoing {
capability(BasicOutgoingCapability(project, sourceSet.name))
// We have two outgoing variants here: the original jar and the classes.
artifact(project.tasks.named(sourceSet.jarTaskName))
variants.create("classes") {
attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.CLASSES))
sourceSet.output.classesDirs.forEach { artifact(it) { builtBy(sourceSet.output) } }
}
}
}
}
companion object {
fun setupBasic(project: Project) {
MinecraftConfigurations(project).setupBasic()
}
fun setup(project: Project) {
MinecraftConfigurations(project).setup()
}
}
}
private class BasicIncomingCapability(private val module: ModuleDependency, private val name: String) : Capability {
override fun getGroup(): String = module.group!!
override fun getName(): String = "${module.name}-$name"
override fun getVersion(): String? = null
}
fun DependencyHandler.clientClasses(notation: Any): ModuleDependency =
Capabilities.clientClasses(create(notation) as ModuleDependency)
private class BasicOutgoingCapability(private val project: Project, private val name: String) : Capability {
override fun getGroup(): String = project.group.toString()
override fun getName(): String = "${project.name}-$name"
override fun getVersion(): String = project.version.toString()
}
fun DependencyHandler.clientClasses(notation: Any): ModuleDependency {
val dep = create(notation) as ModuleDependency
dep.capabilities { requireCapability(BasicIncomingCapability(dep, "client")) }
return dep
}
fun DependencyHandler.commonClasses(notation: Any): ModuleDependency {
val dep = create(notation) as ModuleDependency
dep.capabilities { requireCapability(BasicIncomingCapability(dep, "main")) }
return dep
}
fun DependencyHandler.commonClasses(notation: Any): ModuleDependency =
Capabilities.commonClasses(create(notation) as ModuleDependency)

View File

@@ -4,7 +4,6 @@
package cc.tweaked.gradle
import net.minecraftforge.gradle.common.util.RunConfig
import org.gradle.api.GradleException
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.invocation.Gradle
@@ -65,14 +64,6 @@ abstract class ClientJavaExec : JavaExec() {
setTestProperties()
}
/**
* Set this task to run a given [RunConfig].
*/
fun setRunConfig(config: RunConfig) {
(this as JavaExec).setRunConfig(config)
setTestProperties() // setRunConfig may clobber some properties, ensure everything is set.
}
/**
* Copy configuration from a task with the given name.
*/

View File

@@ -1,51 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package net.minecraftforge.gradle.common.util.runs
import net.minecraftforge.gradle.common.util.RunConfig
import org.gradle.api.Project
import org.gradle.process.CommandLineArgumentProvider
import org.gradle.process.JavaExecSpec
import java.io.File
import java.util.function.Supplier
import java.util.stream.Collectors
import java.util.stream.Stream
/**
* Set up a [JavaExecSpec] to execute a [RunConfig].
*
* [MinecraftRunTask] sets up all its properties when the task is executed, rather than when configured. As such, it's
* not possible to use [cc.tweaked.gradle.copyToFull] like we do for Fabric. Instead, we set up the task manually.
*
* Unfortunately most of the functionality we need is package-private, and so we have to put our code into the package.
*/
internal fun setRunConfigInternal(project: Project, spec: JavaExecSpec, config: RunConfig) {
spec.workingDir = File(config.workingDirectory)
spec.mainClass.set(config.main)
for (source in config.allSources) spec.classpath(source.runtimeClasspath)
val originalTask = project.tasks.named(config.taskName, MinecraftRunTask::class.java)
// Add argument and JVM argument via providers, to be as lazy as possible with fetching artifacts.
val lazyTokens = RunConfigGenerator.configureTokensLazy(
project, config, RunConfigGenerator.mapModClassesToGradle(project, config),
originalTask.get().minecraftArtifacts,
originalTask.get().runtimeClasspathArtifacts,
)
spec.argumentProviders.add(
CommandLineArgumentProvider {
RunConfigGenerator.getArgsStream(config, lazyTokens, false).toList()
},
)
spec.jvmArgumentProviders.add(
CommandLineArgumentProvider {
(if (config.isClient) config.jvmArgs + originalTask.get().additionalClientArgs.get() else config.jvmArgs).map { config.replace(lazyTokens, it) } +
config.properties.map { (k, v) -> "-D${k}=${config.replace(lazyTokens, v)}" }
},
)
for ((key, value) in config.environment) spec.environment(key, config.replace(lazyTokens, value))
}

View File

@@ -21,7 +21,7 @@ of the mod should run fine on later versions.
However, some changes to the underlying game, or CC: Tweaked's own internals may break some programs. This page serves
as documentation for breaking changes and "gotchas" one should look out for between versions.
## CC: Tweaked 1.109.0 to 1.109.2 {#cct-1.109}
## CC: Tweaked 1.109.0 to 1.109.3 {#cct-1.109}
- Update to Lua 5.2:
- Support for Lua 5.0's pseudo-argument `arg` has been removed. You should always use `...` for varargs.
@@ -76,6 +76,6 @@ as documentation for breaking changes and "gotchas" one should look out for betw
- Programs containing `/` are looked up in the current directory and are no longer looked up on the path. For instance,
you can no longer type `turtle/excavate` to run `/rom/programs/turtle/excavate.lua`.
[flattening]: https://minecraft.wiki.com/w/Java_Edition_1.13/Flattening
[flattening]: https://minecraft.wiki/w/Java_Edition_1.13/Flattening
[legal_data_pack]: https://minecraft.gamepedia.com/Tutorials/Creating_a_data_pack#Legal_characters
[datapack-example]: https://github.com/cc-tweaked/datapack-example "An example datapack for CC: Tweaked"

View File

@@ -9,8 +9,8 @@ kotlin.stdlib.default.dependency=false
kotlin.jvm.target.validation.mode=error
# Mod properties
isUnstable=false
modVersion=1.109.3
isUnstable=true
modVersion=1.109.5
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.20.1
mcVersion=1.20.4

View File

@@ -7,25 +7,26 @@
# Minecraft
# MC version is specified in gradle.properties, as we need that in settings.gradle.
# Remember to update corresponding versions in fabric.mod.json/mods.toml
fabric-api = "0.86.1+1.20.1"
fabric-loader = "0.14.21"
forge = "47.1.0"
forgeSpi = "7.0.1"
fabric-api = "0.93.1+1.20.4"
fabric-loader = "0.15.3"
neoForge = "20.4.142-beta"
neoForgeSpi = "8.0.1"
mixin = "0.8.5"
parchment = "2023.08.20"
parchmentMc = "1.20.1"
parchment = "2023.12.31"
parchmentMc = "1.20.3"
yarn = "1.20.4+build.3"
# Core dependencies (these versions are tied to the version Minecraft uses)
fastutil = "8.5.9"
guava = "31.1-jre"
netty = "4.1.82.Final"
slf4j = "2.0.1"
fastutil = "8.5.12"
guava = "32.1.2-jre"
netty = "4.1.97.Final"
slf4j = "2.0.7"
# Core dependencies (independent of Minecraft)
asm = "9.6"
autoService = "1.1.1"
checkerFramework = "3.42.0"
cobalt = "0.8.2"
cobalt = "0.9.0"
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2"
@@ -35,14 +36,14 @@ kotlin-coroutines = "1.7.3"
nightConfig = "3.6.7"
# Minecraft mods
emi = "1.0.8+1.20.1"
emi = "1.0.30+1.20.4"
fabricPermissions = "0.3.20230723"
iris = "1.6.4+1.20"
jei = "15.2.0.22"
modmenu = "7.1.0"
iris = "1.6.14+1.20.4"
jei = "16.0.0.28"
modmenu = "9.0.0"
moreRed = "4.0.0.4"
oculus = "1.2.5"
rei = "12.0.626"
rei = "14.0.688"
rubidium = "0.6.1"
sodium = "mc1.20-0.4.10"
@@ -54,26 +55,23 @@ junit = "5.10.1"
# Build tools
cctJavadoc = "1.8.2"
checkstyle = "10.12.6"
curseForgeGradle = "1.0.14"
curseForgeGradle = "1.1.18"
errorProne-core = "2.23.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.3.9"
forgeGradle = "6.0.8"
fabric-loom = "1.5.7"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-44-g9ee0055"
librarian = "1.+"
lwjgl = "3.3.3"
minotaur = "2.+"
mixinGradle = "0.7.38"
minotaur = "2.8.7"
neoGradle = "7.0.85"
nullAway = "0.9.9"
spotless = "6.23.3"
taskTree = "2.1.1"
teavm = "0.10.0-SQUID.2"
vanillaGradle = "0.2.1-SNAPSHOT"
vanillaExtract = "0.1.1"
versionCatalogUpdate = "0.8.1"
vineflower = "1.11.0"
[libraries]
# Normal dependencies
@@ -84,7 +82,7 @@ checkerFramework = { module = "org.checkerframework:checker-qual", version.ref =
cobalt = { module = "cc.tweaked:cobalt", version.ref = "cobalt" }
commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" }
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
neoForgeSpi = { module = "net.neoforged:neoforgespi", version.ref = "neoForgeSpi" }
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" }
@@ -106,9 +104,9 @@ fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fab
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
jei-api = { module = "mezz.jei:jei-1.20.1-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.20.1-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.20.1-forge", version.ref = "jei" }
jei-api = { module = "mezz.jei:jei-1.20.2-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.20.2-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.20.2-forge", version.ref = "jei" }
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
moreRed = { module = "commoble.morered:morered-1.20.1", version.ref = "moreRed" }
@@ -144,11 +142,10 @@ errorProne-core = { module = "com.google.errorprone:error_prone_core", version.r
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
neoGradle-userdev = { module = "net.neoforged.gradle:userdev", version.ref = "neoGradle" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
@@ -159,16 +156,13 @@ teavm-metaprogramming-api = { module = "org.teavm:teavm-metaprogramming-api", ve
teavm-metaprogramming-impl = { module = "org.teavm:teavm-metaprogramming-impl", version.ref = "teavm" }
teavm-platform = { module = "org.teavm:teavm-platform", version.ref = "teavm" }
teavm-tooling = { module = "org.teavm:teavm-tooling", version.ref = "teavm" }
vanillaGradle = { module = "org.spongepowered:vanillagradle", version.ref = "vanillaGradle" }
vineflower = { module = "io.github.juuxel:loom-vineflower", version.ref = "vineflower" }
vanillaExtract = { module = "cc.tweaked.vanilla-extract:plugin", version.ref = "vanillaExtract" }
yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" }
[plugins]
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
@@ -179,10 +173,9 @@ kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
# Minecraft
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
externalMods-forge-runtime = ["jei-forge"]
externalMods-fabric = ["nightConfig-core", "nightConfig-toml"]
externalMods-forge-runtime = []
externalMods-fabric-compile = ["fabricPermissions", "iris", "jei-api", "rei-api", "rei-builtin"]
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
externalMods-fabric-runtime = []
# Testing
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]

Binary file not shown.

View File

@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

22
gradlew vendored
View File

@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -130,10 +131,13 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -198,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

1376
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@
"rehype-highlight": "^7.0.0",
"rehype-react": "^8.0.0",
"rollup": "^4.0.0",
"tsx": "^3.12.10",
"tsx": "^4.7.0",
"typescript": "^5.2.2"
}
}

View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client.turtle;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
/**
* A functional interface to register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
* <p>
* This interface is largely intended to be used from multi-loader code, to allow sharing registration code between
* multiple loaders.
*/
@FunctionalInterface
public interface RegisterTurtleUpgradeModeller {
/**
* Register a {@link TurtleUpgradeModeller}.
*
* @param serialiser The turtle upgrade serialiser.
* @param modeller The upgrade modeller.
* @param <T> The type of the turtle upgrade.
*/
<T extends ITurtleUpgrade> void register(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
}

View File

@@ -4,13 +4,10 @@
package dan200.computercraft.api.client.turtle;
import dan200.computercraft.api.client.ComputerCraftAPIClient;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
@@ -21,38 +18,27 @@ import java.util.List;
/**
* Provides models for a {@link ITurtleUpgrade}.
* <p>
* Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a
* modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one
* on Forge
*
* @param <T> The type of turtle upgrade this modeller applies to.
* @see ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller) To register a modeller.
* @see RegisterTurtleUpgradeModeller For multi-loader registration support.
*/
public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
/**
* Obtain the model to be used when rendering a turtle peripheral.
* <p>
* When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
* When the current turtle is {@literal null}, this function should be constant for a given upgrade, side and data.
*
* @param upgrade The upgrade that you're getting the model for.
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models, unless
* {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overriden.
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models.
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade.
*/
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
/**
* Obtain the model to be used when rendering a turtle peripheral.
* <p>
* This is used when rendering the turtle's item model, and so no {@link ITurtleAccess} is available.
*
* @param upgrade The upgrade that you're getting the model for.
* @param data Upgrade data instance for current turtle side.
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade.
*/
default TransformedModel getModel(T upgrade, CompoundTag data, TurtleSide side) {
return getModel(upgrade, (ITurtleAccess) null, side);
}
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data);
/**
* Get a list of models that this turtle modeller depends on.
@@ -83,19 +69,6 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.UPGRADE_ITEM;
}
/**
* Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side.
*
* @param left The model to use on the left.
* @param right The model to use on the right.
* @param <T> The type of the turtle upgrade.
* @return The constructed modeller.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
// TODO(1.21.0): Remove this.
return sided((ResourceLocation) left, right);
}
/**
* Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side.
*
@@ -107,7 +80,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
return new TurtleUpgradeModeller<>() {
@Override
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
}

View File

@@ -12,7 +12,6 @@ import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4f;
import javax.annotation.Nullable;
@@ -37,16 +36,8 @@ final class TurtleUpgradeModellers {
private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
return getModel(turtle == null ? upgrade.getCraftingItem() : upgrade.getUpgradeItem(turtle.getUpgradeNBTData(side)), side);
}
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
return getModel(upgrade.getUpgradeItem(data), side);
}
private TransformedModel getModel(ItemStack stack, TurtleSide side) {
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
var stack = upgrade.getUpgradeItem(data);
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);

View File

@@ -4,15 +4,12 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.Map;
/**
* Wrapper class for pocket computers.
@@ -90,13 +87,4 @@ public interface IPocketAccess {
* entity} changes.
*/
void invalidatePeripheral();
/**
* Get a list of all upgrades for the pocket computer.
*
* @return A collection of all upgrade names.
* @deprecated This is a relic of a previous API, which no longer makes sense with newer versions of ComputerCraft.
*/
@Deprecated(forRemoval = true)
Map<ResourceLocation, IPeripheral> getUpgrades();
}

View File

@@ -6,6 +6,10 @@ package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
@@ -13,16 +17,46 @@ import javax.annotation.Nullable;
/**
* A peripheral which can be equipped to the back side of a pocket computer.
* <p>
* Pocket upgrades are defined in two stages. First, on creates a {@link IPocketUpgrade} subclass and corresponding
* {@link PocketUpgradeSerialiser} instance, which are then registered in a Forge registry.
* Pocket upgrades are defined in two stages. First, one creates a {@link IPocketUpgrade} subclass and corresponding
* {@link UpgradeSerialiser} instance, which are then registered in a registry.
* <p>
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
* the upgrade registered internally. See the documentation in {@link PocketUpgradeSerialiser} for details on this process
* and where files should be located.
* the upgrade automatically registered. It is recommended this is done via {@linkplain PocketUpgradeDataProvider data
* generators}.
*
* @see PocketUpgradeSerialiser For how to register a pocket computer upgrade.
* <h2>Example</h2>
* <pre>{@code
* // We use Forge's DeferredRegister to register our serialiser. Fabric mods may register their serialiser directly.
* static final DeferredRegister<UpgradeSerialiser<? extends IPocketUpgrade>> SERIALISERS = DeferredRegister.create(IPocketUpgrade.serialiserRegistryKey(), "my_mod");
*
* // Register a new upgrade serialiser called "my_upgrade".
* public static final RegistryObject<UpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
* SERIALISERS.register("my_upgrade", () -> UpgradeSerialiser.simple(MyUpgrade::new));
*
* // Then in your constructor
* SERIALISERS.register(bus);
* }</pre>
* <p>
* We can then define a new upgrade using JSON by placing the following in
* {@code data/<my_mod>/computercraft/pocket_upgrades/<my_upgrade_id>.json}.
* <pre>{@code
* {
* "type": my_mod:my_upgrade",
* }
* }</pre>
* <p>
* {@link PocketUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
*/
public interface IPocketUpgrade extends UpgradeBase {
/**
* The registry key for upgrade serialisers.
*
* @return The registry key.
*/
static ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> serialiserRegistryKey() {
return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
}
/**
* Creates a peripheral for the pocket computer.
* <p>

View File

@@ -5,6 +5,7 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
@@ -17,10 +18,11 @@ import java.util.function.Consumer;
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
* generate them.
*
* @see PocketUpgradeSerialiser
* @see IPocketUpgrade
* @see UpgradeSerialiser
*/
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>> {
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade> {
public PocketUpgradeDataProvider(PackOutput output) {
super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.registryId());
super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", IPocketUpgrade.serialiserRegistryKey());
}
}

View File

@@ -1,79 +0,0 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Reads a {@link IPocketUpgrade} from disk and reads/writes it to a network packet.
* <p>
* This follows the same format as {@link dan200.computercraft.api.turtle.TurtleUpgradeSerialiser} - consult the
* documentation there for more information.
*
* @param <T> The type of pocket computer upgrade this is responsible for serialising.
* @see IPocketUpgrade
* @see PocketUpgradeDataProvider
*/
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
*
* @return The registry key.
*/
static ResourceKey<Registry<PocketUpgradeSerialiser<?>>> registryId() {
return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
}
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.
* <p>
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
*
* @param factory Generate a new upgrade with a specific ID.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade
*/
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
final class Impl extends SimpleSerialiser<T> implements PocketUpgradeSerialiser<T> {
private Impl(Function<ResourceLocation, T> constructor) {
super(constructor);
}
}
return new Impl(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
final class Impl extends SerialiserWithCraftingItem<T> implements PocketUpgradeSerialiser<T> {
private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
super(factory);
}
}
return new Impl(factory);
}
}

View File

@@ -6,8 +6,12 @@ package dan200.computercraft.api.turtle;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import javax.annotation.Nullable;
@@ -16,15 +20,54 @@ import javax.annotation.Nullable;
* peripheral.
* <p>
* Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding
* {@link TurtleUpgradeSerialiser} instance, which are then registered in a Forge registry.
* {@link UpgradeSerialiser} instance, which are then registered in a registry.
* <p>
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
* the upgrade registered internally. See the documentation in {@link TurtleUpgradeSerialiser} for details on this process
* and where files should be located.
* the upgrade automatically registered. It is recommended this is done via {@linkplain TurtleUpgradeDataProvider data
* generators}.
*
* @see TurtleUpgradeSerialiser For how to register a turtle upgrade.
* <h2>Example</h2>
* <pre>{@code
* // We use Forge's DeferredRegister to register our serialiser. Fabric mods may register their serialiser directly.
* static final DeferredRegister<UpgradeSerialiser<? extends ITurtleUpgrade>> SERIALISERS = DeferredRegister.create(ITurtleUpgrade.serialiserRegistryKey(), "my_mod");
*
* // Register a new upgrade serialiser called "my_upgrade".
* public static final RegistryObject<UpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
* SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
*
* // Then in your constructor
* SERIALISERS.register( bus );
* }</pre>
* <p>
* We can then define a new upgrade using JSON by placing the following in
* {@literal data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}}.
*
* <pre>{@code
* {
* "type": my_mod:my_upgrade",
* }
* }</pre>
* <p>
* {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
* <p>
* Finally, we need to register a model for our upgrade, see
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for more information.
*
* <pre>{@code
* // Register our model inside FMLClientSetupEvent
* ComputerCraftAPIClient.registerTurtleUpgradeModeller(MY_UPGRADE.get(), TurtleUpgradeModeller.flatItem())
* }</pre>
*/
public interface ITurtleUpgrade extends UpgradeBase {
/**
* The registry key for upgrade serialisers.
*
* @return The registry key.
*/
static ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> serialiserRegistryKey() {
return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
}
/**
* Return whether this turtle adds a tool or a peripheral to the turtle.
*

View File

@@ -7,8 +7,9 @@ package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.core.registries.Registries;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.RegistryHelper;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
@@ -29,13 +30,13 @@ import java.util.function.Consumer;
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
* generate them.
*
* @see TurtleUpgradeSerialiser
* @see ITurtleUpgrade
*/
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade> {
private static final ResourceLocation TOOL_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "tool");
public TurtleUpgradeDataProvider(PackOutput output) {
super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.registryId());
super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", ITurtleUpgrade.serialiserRegistryKey());
}
/**
@@ -57,7 +58,7 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
*/
public static class ToolBuilder {
private final ResourceLocation id;
private final TurtleUpgradeSerialiser<?> serialiser;
private final UpgradeSerialiser<? extends ITurtleUpgrade> serialiser;
private final Item toolItem;
private @Nullable String adjective;
private @Nullable Item craftingItem;
@@ -66,7 +67,7 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
private boolean allowEnchantments = false;
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
ToolBuilder(ResourceLocation id, UpgradeSerialiser<? extends ITurtleUpgrade> serialiser, Item toolItem) {
this.id = id;
this.serialiser = serialiser;
this.toolItem = toolItem;
@@ -149,12 +150,12 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
*
* @param add The callback given to {@link #addUpgrades(Consumer)}.
*/
public void add(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add) {
public void add(Consumer<Upgrade<UpgradeSerialiser<? extends ITurtleUpgrade>>> add) {
add.accept(new Upgrade<>(id, serialiser, s -> {
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, toolItem).toString());
s.addProperty("item", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, toolItem).toString());
if (adjective != null) s.addProperty("adjective", adjective);
if (craftingItem != null) {
s.addProperty("craftItem", PlatformHelper.get().getRegistryKey(Registries.ITEM, craftingItem).toString());
s.addProperty("craftItem", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, craftingItem).toString());
}
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
if (breakable != null) s.addProperty("breakable", breakable.location().toString());

View File

@@ -1,114 +0,0 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Reads a {@link ITurtleUpgrade} from disk and reads/writes it to a network packet.
* <p>
* These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
* <p>
* If your turtle upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
* {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
*
* <h2>Example (Forge)</h2>
* <pre>{@code
* static final DeferredRegister<TurtleUpgradeSerialiser<?>> SERIALISERS = DeferredRegister.create( TurtleUpgradeSerialiser.TYPE, "my_mod" );
*
* // Register a new upgrade serialiser called "my_upgrade".
* public static final RegistryObject<TurtleUpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
* SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
*
* // Then in your constructor
* SERIALISERS.register( bus );
* }</pre>
* <p>
* We can then define a new upgrade using JSON by placing the following in
* {@literal data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}}.
*
* <pre>{@code
* {
* "type": my_mod:my_upgrade",
* }
* }</pre>
* <p>
* Finally, we need to register a model for our upgrade. This is done with
* {@link dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller}:
*
* <pre>{@code
* // Register our model inside FMLClientSetupEvent
* ComputerCraftAPIClient.registerTurtleUpgradeModeller(MY_UPGRADE.get(), TurtleUpgradeModeller.flatItem())
* }</pre>
* <p>
* {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
*
* @param <T> The type of turtle upgrade this is responsible for serialising.
* @see ITurtleUpgrade
* @see TurtleUpgradeDataProvider
* @see dan200.computercraft.api.client.turtle.TurtleUpgradeModeller
*/
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
*
* @return The registry key.
*/
static ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> registryId() {
return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
}
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.
* <p>
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
*
* @param factory Generate a new upgrade with a specific ID.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade
*/
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
final class Impl extends SimpleSerialiser<T> implements TurtleUpgradeSerialiser<T> {
private Impl(Function<ResourceLocation, T> constructor) {
super(constructor);
}
}
return new Impl(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
final class Impl extends SerialiserWithCraftingItem<T> implements TurtleUpgradeSerialiser<T> {
private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
super(factory);
}
}
return new Impl(factory);
}
}

View File

@@ -9,7 +9,6 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
@@ -100,7 +99,7 @@ public interface UpgradeBase {
* The default check requires that any non-capability NBT is exactly the same as the
* crafting item, but this may be relaxed for your upgrade.
* <p>
* This is based on {@code net.minecraftforge.common.crafting.StrictNBTIngredient}'s check.
* This is based on {@code net.neoforged.common.crafting.StrictNBTIngredient}'s check.
*
* @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
* {@link #getCraftingItem()}.
@@ -111,12 +110,12 @@ public interface UpgradeBase {
// A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
// null one.
var shareTag = PlatformHelper.get().getShareTag(stack);
var craftingShareTag = PlatformHelper.get().getShareTag(crafting);
if (shareTag == craftingShareTag) return true;
if (shareTag == null) return Objects.requireNonNull(craftingShareTag).isEmpty();
if (craftingShareTag == null) return shareTag.isEmpty();
return shareTag.equals(craftingShareTag);
var tag = stack.getTag();
var craftingTag = crafting.getTag();
if (tag == craftingTag) return true;
if (tag == null) return Objects.requireNonNull(craftingTag).isEmpty();
if (craftingTag == null) return tag.isEmpty();
return tag.equals(craftingTag);
}
/**

View File

@@ -6,19 +6,20 @@ package dan200.computercraft.api.upgrades;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.PlatformHelper;
import dan200.computercraft.impl.RegistryHelper;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.Util;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import java.util.*;
@@ -31,31 +32,31 @@ import java.util.function.Function;
* the other subclasses.
*
* @param <T> The base class of upgrades.
* @param <R> The upgrade serialiser to register for.
*/
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
public abstract class UpgradeDataProvider<T extends UpgradeBase> implements DataProvider {
private final PackOutput output;
private final String name;
private final String folder;
private final ResourceKey<Registry<R>> registry;
private final Registry<UpgradeSerialiser<? extends T>> registry;
private @Nullable List<T> upgrades;
protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<R>> registry) {
@ApiStatus.Internal
protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry) {
this.output = output;
this.name = name;
this.folder = folder;
this.registry = registry;
this.registry = RegistryHelper.getRegistry(registry);
}
/**
* Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
* Register an upgrade using a {@linkplain UpgradeSerialiser#simple(Function) "simple" serialiser}.
*
* @param id The ID of the upgrade to create.
* @param serialiser The simple serialiser.
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
*/
public final Upgrade<R> simple(ResourceLocation id, R serialiser) {
public final Upgrade<UpgradeSerialiser<? extends T>> simple(ResourceLocation id, UpgradeSerialiser<? extends T> serialiser) {
if (!(serialiser instanceof SimpleSerialiser)) {
throw new IllegalStateException(serialiser + " must be a simple() seriaiser.");
}
@@ -65,20 +66,20 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
}
/**
* Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
* Register an upgrade using a {@linkplain UpgradeSerialiser#simple(Function) simple serialiser}.
*
* @param id The ID of the upgrade to create.
* @param serialiser The simple serialiser.
* @param item The crafting upgrade for this item.
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
*/
public final Upgrade<R> simpleWithCustomItem(ResourceLocation id, R serialiser, Item item) {
public final Upgrade<UpgradeSerialiser<? extends T>> simpleWithCustomItem(ResourceLocation id, UpgradeSerialiser<? extends T> serialiser, Item item) {
if (!(serialiser instanceof SerialiserWithCraftingItem)) {
throw new IllegalStateException(serialiser + " must be a simpleWithCustomItem() serialiser.");
}
return new Upgrade<>(id, serialiser, s ->
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, item).toString())
s.addProperty("item", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, item).toString())
);
}
@@ -94,7 +95,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
*
* @param addUpgrade A callback used to register an upgrade.
*/
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
protected abstract void addUpgrades(Consumer<Upgrade<UpgradeSerialiser<? extends T>>> addUpgrade);
@Override
public CompletableFuture<?> run(CachedOutput cache) {
@@ -107,7 +108,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
if (!seen.add(upgrade.id())) throw new IllegalStateException("Duplicate upgrade " + upgrade.id());
var json = new JsonObject();
json.addProperty("type", PlatformHelper.get().getRegistryKey(registry, upgrade.serialiser()).toString());
json.addProperty("type", RegistryHelper.getKeyOrThrow(registry, upgrade.serialiser()).toString());
upgrade.serialise().accept(json);
futures.add(DataProvider.saveStable(cache, json, base.resolve(upgrade.id().getNamespace() + "/" + folder + "/" + upgrade.id().getPath() + ".json")));
@@ -129,9 +130,9 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
return name;
}
public final R existingSerialiser(ResourceLocation id) {
var result = PlatformHelper.get().getRegistryObject(registry, id);
if (result == null) throw new IllegalArgumentException("No such serialiser " + registry);
public final UpgradeSerialiser<? extends T> existingSerialiser(ResourceLocation id) {
var result = registry.get(id);
if (result == null) throw new IllegalArgumentException("No such serialiser " + id);
return result;
}

View File

@@ -5,21 +5,40 @@
package dan200.computercraft.api.upgrades;
import com.google.gson.JsonObject;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Base interface for upgrade serialisers. This should generally not be implemented directly, instead implementing one
* of {@link TurtleUpgradeSerialiser} or {@link PocketUpgradeSerialiser}.
* A serialiser for {@link ITurtleUpgrade} or {@link IPocketUpgrade}s.
* <p>
* However, it may sometimes be useful to implement this if you have some shared logic between upgrade types.
* These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
* <p>
* This interface is very similar to {@link RecipeSerializer}; each serialiser should correspond to a specific upgrade
* class. Upgrades are then read from JSON files in datapacks, allowing multiple instances of the upgrade to be
* registered.
* <p>
* If your upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
* {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
* <p>
* Upgrades may be data generated via a {@link UpgradeDataProvider} (see {@link TurtleUpgradeDataProvider} and
* {@link PocketUpgradeDataProvider}).
*
* @param <T> The upgrade that this class can serialise and deserialise.
* @see TurtleUpgradeSerialiser
* @see PocketUpgradeSerialiser
* @see ITurtleUpgrade
* @see IPocketUpgrade
*/
public interface UpgradeSerialiser<T extends UpgradeBase> {
/**
@@ -49,4 +68,30 @@ public interface UpgradeSerialiser<T extends UpgradeBase> {
*/
void toNetwork(FriendlyByteBuf buffer, T upgrade);
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.
* <p>
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
*
* @param factory Generate a new upgrade with a specific ID.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade
*/
static <T extends UpgradeBase> UpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
return new SimpleSerialiser<>(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
static <T extends UpgradeBase> UpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
return new SerialiserWithCraftingItem<>(factory);
}
}

View File

@@ -15,10 +15,11 @@ import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
@@ -67,9 +68,9 @@ public interface ComputerCraftAPIService {
void registerRefuelHandler(TurtleRefuelHandler handler);
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId();
ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId();
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId();
ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId();
DetailRegistry<ItemStack> getItemStackDetailRegistry();

View File

@@ -6,11 +6,6 @@ package dan200.computercraft.impl;
import com.google.gson.JsonObject;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
@@ -32,39 +27,6 @@ public interface PlatformHelper {
return instance == null ? Services.raise(PlatformHelper.class, Instance.ERROR) : instance;
}
/**
* Get the unique ID for a registered object.
*
* @param registry The registry to look up this object in.
* @param object The object to look up.
* @param <T> The type of object the registry stores.
* @return The registered object's ID.
* @throws IllegalArgumentException If the registry or object are not registered.
*/
<T> ResourceLocation getRegistryKey(ResourceKey<Registry<T>> registry, T object);
/**
* Look up an ID in a registry, returning the registered object.
*
* @param registry The registry to look up this object in.
* @param id The ID to look up.
* @param <T> The type of object the registry stores.
* @return The resolved registry object.
* @throws IllegalArgumentException If the registry or object are not registered.
*/
<T> T getRegistryObject(ResourceKey<Registry<T>> registry, ResourceLocation id);
/**
* Get the subset of an {@link ItemStack}'s {@linkplain ItemStack#getTag() tag} which is synced to the client.
*
* @param item The stack.
* @return The item's tag.
*/
@Nullable
default CompoundTag getShareTag(ItemStack item) {
return item.getTag();
}
/**
* Add a resource condition which requires a mod to be loaded. This should be used by data providers such as
* {@link UpgradeDataProvider}.

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.impl;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
/**
* Additioanl functions for working with {@linkplain Registry registries}.
*/
@ApiStatus.Internal
public final class RegistryHelper {
private RegistryHelper() {
}
/**
* Find a registry from a {@link ResourceKey}, throwing if it does not exist.
*
* @param id The id of the registry.
* @param <T> The contents of the registry
* @return The associated registry.
*/
@SuppressWarnings("unchecked")
public static <T> Registry<T> getRegistry(ResourceKey<Registry<T>> id) {
var registry = (Registry<T>) BuiltInRegistries.REGISTRY.get(id.location());
if (registry == null) throw new IllegalArgumentException("Unknown registry " + id);
return registry;
}
/**
* Get the key of a registry entry, throwing if it is not registered.
*
* @param registry The registry to look up in.
* @param object The object to look up.
* @param <T> The type of this registry.
* @return The ID of this object
* @see Registry#getResourceKey(Object)
*/
public static <T> ResourceLocation getKeyOrThrow(Registry<T> registry, T object) {
var key = registry.getResourceKey(object);
if (key.isEmpty()) throw new IllegalArgumentException(object + " was not registered in " + registry.key());
return key.get().location();
}
}

View File

@@ -23,27 +23,27 @@ import java.util.function.BiFunction;
* @param <T> The upgrade that this class can serialise and deserialise.
*/
@ApiStatus.Internal
public abstract class SerialiserWithCraftingItem<T extends UpgradeBase> implements UpgradeSerialiser<T> {
public final class SerialiserWithCraftingItem<T extends UpgradeBase> implements UpgradeSerialiser<T> {
private final BiFunction<ResourceLocation, ItemStack, T> factory;
protected SerialiserWithCraftingItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
public SerialiserWithCraftingItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
this.factory = factory;
}
@Override
public final T fromJson(ResourceLocation id, JsonObject object) {
public T fromJson(ResourceLocation id, JsonObject object) {
var item = GsonHelper.getAsItem(object, "item");
return factory.apply(id, new ItemStack(item));
}
@Override
public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
var item = buffer.readItem();
return factory.apply(id, item);
}
@Override
public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
public void toNetwork(FriendlyByteBuf buffer, T upgrade) {
buffer.writeItem(upgrade.getCraftingItem());
}
}

View File

@@ -21,7 +21,7 @@ import java.util.function.Function;
* @param <T> The upgrade that this class can serialise and deserialise.
*/
@ApiStatus.Internal
public abstract class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSerialiser<T> {
public final class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSerialiser<T> {
private final Function<ResourceLocation, T> constructor;
public SimpleSerialiser(Function<ResourceLocation, T> constructor) {
@@ -29,16 +29,16 @@ public abstract class SimpleSerialiser<T extends UpgradeBase> implements Upgrade
}
@Override
public final T fromJson(ResourceLocation id, JsonObject object) {
public T fromJson(ResourceLocation id, JsonObject object) {
return constructor.apply(id);
}
@Override
public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
return constructor.apply(id);
}
@Override
public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
public void toNetwork(FriendlyByteBuf buffer, T upgrade) {
}
}

View File

@@ -22,6 +22,14 @@ configurations {
register("cctJavadoc")
}
repositories {
maven("https://maven.minecraftforge.net/") {
content {
includeModule("org.spongepowered", "mixin")
}
}
}
dependencies {
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
implementation(project(":core"))
@@ -31,7 +39,6 @@ dependencies {
compileOnly(libs.bundles.externalMods.common)
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
compileOnly(libs.mixin)
annotationProcessorEverywhere(libs.autoService)
testFixturesAnnotationProcessor(libs.autoService)
@@ -39,6 +46,7 @@ dependencies {
testImplementation(libs.bundles.test)
testRuntimeOnly(libs.bundles.testRuntime)
testModCompileOnly(libs.mixin)
testModImplementation(testFixtures(project(":core")))
testModImplementation(testFixtures(project(":common")))
testModImplementation(libs.bundles.kotlin)
@@ -72,7 +80,7 @@ val luaJavadoc by tasks.registering(Javadoc::class) {
javadocTool.set(
javaToolchains.javadocToolFor {
languageVersion.set(cc.tweaked.gradle.CCTweakedPlugin.JAVA_VERSION)
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
},
)
}

View File

@@ -17,8 +17,6 @@ import dan200.computercraft.client.render.monitor.MonitorRenderState;
import dan200.computercraft.client.sound.SpeakerManager;
import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
@@ -28,7 +26,6 @@ import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.PauseAwareTimer;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.Util;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
@@ -43,7 +40,6 @@ import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import javax.annotation.Nullable;
import java.io.File;
import java.util.function.Consumer;
/**
@@ -71,10 +67,6 @@ public final class ClientHooks {
ClientPocketComputers.reset();
}
public static boolean onChatMessage(String message) {
return handleOpenComputerCommand(message);
}
public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
return CableHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit)
|| MonitorHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit);
@@ -109,34 +101,6 @@ public final class ClientHooks {
SpeakerManager.onPlayStreaming(engine, channel, stream);
}
/**
* Handle the {@link CommandComputerCraft#OPEN_COMPUTER} "clientside command". This isn't a true command, as we
* don't want it to actually be visible to the user.
*
* @param message The current chat message.
* @return Whether to cancel sending this message.
*/
private static boolean handleOpenComputerCommand(String message) {
if (!message.startsWith(CommandComputerCraft.OPEN_COMPUTER)) return false;
var server = Minecraft.getInstance().getSingleplayerServer();
if (server == null) return false;
var idStr = message.substring(CommandComputerCraft.OPEN_COMPUTER.length()).trim();
int id;
try {
id = Integer.parseInt(idStr);
} catch (NumberFormatException ignore) {
return false;
}
var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
if (!file.isDirectory()) return false;
Util.getPlatform().openFile(file);
return true;
}
/**
* Add additional information about the currently targeted block to the debug screen.
*
@@ -144,7 +108,7 @@ public final class ClientHooks {
*/
public static void addBlockDebugInfo(Consumer<String> addText) {
var minecraft = Minecraft.getInstance();
if (!minecraft.options.renderDebug || minecraft.level == null) return;
if (!minecraft.getDebugOverlay().showDebugScreen() || minecraft.level == null) return;
if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;
var tile = minecraft.level.getBlockEntity(((BlockHitResult) minecraft.hitResult).getBlockPos());
@@ -174,7 +138,7 @@ public final class ClientHooks {
* @param addText A callback which adds a single line of text.
*/
public static void addGameDebugInfo(Consumer<String> addText) {
if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().options.renderDebug) {
if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().getDebugOverlay().showDebugScreen()) {
addText.accept("[CC:T] Monitor renderer: " + MonitorBlockEntityRenderer.currentRenderer());
}
}

View File

@@ -4,8 +4,12 @@
package dan200.computercraft.client;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.ComputerCraftAPIClient;
import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.pocket.ClientPocketComputers;
@@ -16,29 +20,38 @@ import dan200.computercraft.client.turtle.TurtleModemModeller;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.media.items.TreasureDiskItem;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.color.item.ItemColor;
import net.minecraft.client.gui.screens.MenuScreens;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.MenuAccess;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ResourceProvider;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -60,18 +73,6 @@ public final class ClientRegistry {
* Register any client-side objects which don't have to be done on the main thread.
*/
public static void register() {
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided(
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), MonitorBlockEntityRenderer::new);
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new);
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new);
@@ -82,17 +83,6 @@ public final class ClientRegistry {
* Register any client-side objects which must be done on the main thread.
*/
public static void registerMainThread() {
MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), ComputerScreen::new);
MenuScreens.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
MenuScreens.register(ModRegistry.Menus.TURTLE.get(), TurtleScreen::new);
MenuScreens.register(ModRegistry.Menus.PRINTER.get(), PrinterScreen::new);
MenuScreens.register(ModRegistry.Menus.DISK_DRIVE.get(), DiskDriveScreen::new);
MenuScreens.register(ModRegistry.Menus.PRINTOUT.get(), PrintoutScreen::new);
MenuScreens.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
registerItemProperty("state",
new UnclampedPropertyFunction((stack, world, player, random) -> ClientPocketComputers.get(stack).getState().ordinal()),
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
@@ -103,6 +93,37 @@ public final class ClientRegistry {
);
}
public static void registerMenuScreens(RegisterMenuScreen register) {
register.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
register.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), ComputerScreen::new);
register.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
register.register(ModRegistry.Menus.TURTLE.get(), TurtleScreen::new);
register.register(ModRegistry.Menus.PRINTER.get(), PrinterScreen::new);
register.register(ModRegistry.Menus.DISK_DRIVE.get(), DiskDriveScreen::new);
register.register(ModRegistry.Menus.PRINTOUT.get(), PrintoutScreen::new);
register.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
}
public interface RegisterMenuScreen {
<M extends AbstractContainerMenu, U extends Screen & MenuAccess<M>> void register(MenuType<? extends M> type, MenuScreens.ScreenConstructor<M, U> factory);
}
public static void registerTurtleModellers(RegisterTurtleUpgradeModeller register) {
register.register(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
));
register.register(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided(
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
));
register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
register.register(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
}
@SafeVarargs
private static void registerItemProperty(String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name);
@@ -179,4 +200,45 @@ public final class ClientRegistry {
return function.unclampedCall(stack, level, entity, layer);
}
}
/**
* Register client-side commands.
*
* @param dispatcher The dispatcher to register the commands to.
* @param sendError A function to send an error message.
* @param <T> The type of the client-side command context.
*/
public static <T> void registerClientCommands(CommandDispatcher<T> dispatcher, BiConsumer<T, Component> sendError) {
dispatcher.register(LiteralArgumentBuilder.<T>literal(CommandComputerCraft.CLIENT_OPEN_FOLDER)
.requires(x -> Minecraft.getInstance().getSingleplayerServer() != null)
.then(RequiredArgumentBuilder.<T, Integer>argument("computer_id", IntegerArgumentType.integer(0))
.executes(c -> handleOpenComputerCommand(c.getSource(), sendError, c.getArgument("computer_id", Integer.class)))
));
}
/**
* Handle the {@link CommandComputerCraft#CLIENT_OPEN_FOLDER} command.
*
* @param context The command context.
* @param sendError A function to send an error message.
* @param id The computer's id.
* @param <T> The type of the client-side command context.
* @return {@code 1} if a folder was opened, {@code 0} otherwise.
*/
private static <T> int handleOpenComputerCommand(T context, BiConsumer<T, Component> sendError, int id) {
var server = Minecraft.getInstance().getSingleplayerServer();
if (server == null) {
sendError.accept(context, Component.literal("Not on a single-player server"));
return 0;
}
var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
if (!file.isDirectory()) {
sendError.accept(context, Component.literal("Computer's folder does not exist"));
return 0;
}
Util.getPlatform().openFile(file);
return 1;
}
}

View File

@@ -7,7 +7,7 @@ package dan200.computercraft.client.gui;
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
import dan200.computercraft.client.gui.widgets.DynamicImageButton;
import dan200.computercraft.client.gui.widgets.TerminalWidget;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.network.ClientNetworking;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.InputHandler;
@@ -125,7 +125,6 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(graphics, mouseX, mouseY);
}
@@ -207,7 +206,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
return;
}
if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, ClientPlatformHelper.get()::sendToServer);
if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, ClientNetworking::sendToServer);
}
public void uploadResult(UploadResult result, @Nullable Component message) {

View File

@@ -4,7 +4,7 @@
package dan200.computercraft.client.gui;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.network.ClientNetworking;
import dan200.computercraft.shared.computer.core.InputHandler;
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
@@ -29,51 +29,51 @@ public final class ClientInputHandler implements InputHandler {
@Override
public void turnOn() {
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
}
@Override
public void shutdown() {
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.SHUTDOWN));
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.SHUTDOWN));
}
@Override
public void reboot() {
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
}
@Override
public void queueEvent(String event, @Nullable Object[] arguments) {
ClientPlatformHelper.get().sendToServer(new QueueEventServerMessage(menu, event, arguments));
ClientNetworking.sendToServer(new QueueEventServerMessage(menu, event, arguments));
}
@Override
public void keyDown(int key, boolean repeat) {
ClientPlatformHelper.get().sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
}
@Override
public void keyUp(int key) {
ClientPlatformHelper.get().sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
}
@Override
public void mouseClick(int button, int x, int y) {
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
}
@Override
public void mouseUp(int button, int x, int y) {
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
}
@Override
public void mouseDrag(int button, int x, int y) {
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
}
@Override
public void mouseScroll(int direction, int x, int y) {
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
}
}

View File

@@ -28,7 +28,6 @@ public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(graphics, mouseX, mouseY);
}

View File

@@ -9,6 +9,7 @@ import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.toasts.Toast;
import net.minecraft.client.gui.components.toasts.ToastComponent;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.world.item.ItemStack;
@@ -18,6 +19,7 @@ import java.util.List;
* A {@link Toast} implementation which displays an arbitrary message along with an optional {@link ItemStack}.
*/
public class ItemToast implements Toast {
private static final ResourceLocation TEXTURE = new ResourceLocation("toast/recipe");
public static final Object TRANSFER_NO_RESPONSE_TOKEN = new Object();
private static final long DISPLAY_TIME = 7000L;
@@ -79,7 +81,7 @@ public class ItemToast implements Toast {
}
if (width == 160 && message.size() <= 1) {
graphics.blit(TEXTURE, 0, 0, 0, 64, width, height());
graphics.blitSprite(TEXTURE, 0, 0, width, height());
} else {
var height = height();
@@ -109,14 +111,14 @@ public class ItemToast implements Toast {
}
private static void renderBackgroundRow(GuiGraphics graphics, int x, int u, int y, int height) {
var leftOffset = 5;
var leftOffset = u == 0 ? 20 : 5;
var rightOffset = Math.min(60, x - leftOffset);
graphics.blit(TEXTURE, 0, y, 0, 32 + u, leftOffset, height);
graphics.blitSprite(TEXTURE, 160, 32, 0, u, 0, y, leftOffset, height);
for (var k = leftOffset; k < x - rightOffset; k += 64) {
graphics.blit(TEXTURE, k, y, 32, 32 + u, Math.min(64, x - k - rightOffset), height);
graphics.blitSprite(TEXTURE, 160, 32, 32, u, k, y, Math.min(64, x - k - rightOffset), height);
}
graphics.blit(TEXTURE, x - rightOffset, y, 160 - rightOffset, 32 + u, rightOffset, height);
graphics.blitSprite(TEXTURE, 160, 32, 160 - rightOffset, u, x - rightOffset, y, rightOffset, height);
}
}

View File

@@ -63,9 +63,9 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
}
@Override
public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) {
minecraft.player.getInventory().swapPaint(pDelta);
return super.mouseScrolled(pMouseX, pMouseY, pDelta);
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
minecraft.player.getInventory().swapPaint(scrollX);
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
}
@Override

View File

@@ -86,8 +86,6 @@ public final class OptionScreen extends Screen {
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
// Render the actual texture.
graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING);
graphics.blit(BACKGROUND,

View File

@@ -30,7 +30,6 @@ public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(graphics, mouseX, mouseY);
}

View File

@@ -64,15 +64,15 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
}
@Override
public boolean mouseScrolled(double x, double y, double delta) {
if (super.mouseScrolled(x, y, delta)) return true;
if (delta < 0) {
public boolean mouseScrolled(double x, double y, double deltaX, double deltaY) {
if (super.mouseScrolled(x, y, deltaX, deltaY)) return true;
if (deltaX < 0) {
// Scroll up goes to the next page
if (page < pages - 1) page++;
return true;
}
if (delta > 0) {
if (deltaX > 0) {
// Scroll down goes to the previous page
if (page > 0) page--;
return true;
@@ -91,14 +91,12 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
}
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
public void renderBackground(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
// We must take the background further back in order to not overlap with our printed pages.
graphics.pose().pushPose();
graphics.pose().translate(0, 0, -1);
renderBackground(graphics);
super.renderBackground(graphics, mouseX, mouseY, partialTicks);
graphics.pose().popPose();
super.render(graphics, mouseX, mouseY, partialTicks);
}
@Override

View File

@@ -43,6 +43,10 @@ public class DynamicImageButton extends Button {
@Override
public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
var message = this.message.get();
setMessage(message.message());
setTooltip(message.tooltip());
var texture = this.texture.get(isHoveredOrFocused());
RenderSystem.disableDepthTest();
@@ -50,14 +54,6 @@ public class DynamicImageButton extends Button {
RenderSystem.enableDepthTest();
}
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
var message = this.message.get();
setMessage(message.message());
setTooltip(message.tooltip());
super.render(graphics, mouseX, mouseY, partialTicks);
}
public record HintedMessage(Component message, Tooltip tooltip) {
public HintedMessage(Component message, @Nullable Component hint) {
this(

View File

@@ -195,7 +195,7 @@ public class TerminalWidget extends AbstractWidget {
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
public boolean mouseScrolled(double mouseX, double mouseY, double delta, double deltaY) {
if (!inTermRegion(mouseX, mouseY)) return false;
if (!hasMouseSupport() || delta == 0) return false;

View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.network;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import net.minecraft.client.Minecraft;
/**
* Methods for sending packets from clients to the server.
*/
public final class ClientNetworking {
private ClientNetworking() {
}
/**
* Send a network message to the server.
*
* @param message The message to send.
*/
public static void sendToServer(NetworkMessage<ServerNetworkContext> message) {
var connection = Minecraft.getInstance().getConnection();
if (connection != null) connection.send(ClientPlatformHelper.get().createPacket(message));
}
}

View File

@@ -4,6 +4,7 @@
package dan200.computercraft.client.platform;
import com.google.auto.service.AutoService;
import dan200.computercraft.client.ClientTableFormatter;
import dan200.computercraft.client.gui.AbstractComputerScreen;
import dan200.computercraft.client.gui.OptionScreen;
@@ -17,30 +18,30 @@ import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import io.netty.buffer.ByteBuf;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.UUID;
/**
* The base implementation of {@link ClientNetworkContext}.
* <p>
* This should be extended by mod loader specific modules with the remaining abstract methods.
* The client-side implementation of {@link ClientNetworkContext}.
*/
public abstract class AbstractClientNetworkContext implements ClientNetworkContext {
@AutoService(ClientNetworkContext.class)
public final class ClientNetworkContextImpl implements ClientNetworkContext {
@Override
public final void handleChatTable(TableBuilder table) {
public void handleChatTable(TableBuilder table) {
ClientTableFormatter.INSTANCE.display(table);
}
@Override
public final void handleComputerTerminal(int containerId, TerminalState terminal) {
public void handleComputerTerminal(int containerId, TerminalState terminal) {
Player player = Minecraft.getInstance().player;
if (player != null && player.containerMenu.containerId == containerId && player.containerMenu instanceof ComputerMenu menu) {
menu.updateTerminal(terminal);
@@ -48,7 +49,7 @@ public abstract class AbstractClientNetworkContext implements ClientNetworkConte
}
@Override
public final void handleMonitorData(BlockPos pos, TerminalState terminal) {
public void handleMonitorData(BlockPos pos, TerminalState terminal) {
var player = Minecraft.getInstance().player;
if (player == null) return;
@@ -59,44 +60,46 @@ public abstract class AbstractClientNetworkContext implements ClientNetworkConte
}
@Override
public final void handlePocketComputerData(int instanceId, ComputerState state, int lightState, TerminalState terminal) {
public void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable String name) {
var mc = Minecraft.getInstance();
ClientPlatformHelper.get().playStreamingMusic(pos, sound);
if (name != null) mc.gui.setNowPlaying(Component.literal(name));
}
@Override
public void handlePocketComputerData(int instanceId, ComputerState state, int lightState, TerminalState terminal) {
var computer = ClientPocketComputers.get(instanceId, terminal.colour);
computer.setState(state, lightState);
if (terminal.hasTerminal()) computer.setTerminal(terminal);
}
@Override
public final void handlePocketComputerDeleted(int instanceId) {
public void handlePocketComputerDeleted(int instanceId) {
ClientPocketComputers.remove(instanceId);
}
@Override
public final void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume) {
SpeakerManager.getSound(source).playAudio(reifyPosition(position), volume);
public void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume, ByteBuffer buffer) {
SpeakerManager.getSound(source).playAudio(reifyPosition(position), volume, buffer);
}
@Override
public final void handleSpeakerAudioPush(UUID source, ByteBuf buffer) {
SpeakerManager.getSound(source).pushAudio(buffer);
}
@Override
public final void handleSpeakerMove(UUID source, SpeakerPosition.Message position) {
public void handleSpeakerMove(UUID source, SpeakerPosition.Message position) {
SpeakerManager.moveSound(source, reifyPosition(position));
}
@Override
public final void handleSpeakerPlay(UUID source, SpeakerPosition.Message position, ResourceLocation sound, float volume, float pitch) {
public void handleSpeakerPlay(UUID source, SpeakerPosition.Message position, ResourceLocation sound, float volume, float pitch) {
SpeakerManager.getSound(source).playSound(reifyPosition(position), sound, volume, pitch);
}
@Override
public final void handleSpeakerStop(UUID source) {
public void handleSpeakerStop(UUID source) {
SpeakerManager.stopSound(source);
}
@Override
public final void handleUploadResult(int containerId, UploadResult result, @Nullable Component errorMessage) {
public void handleUploadResult(int containerId, UploadResult result, @Nullable Component errorMessage) {
var minecraft = Minecraft.getInstance();
var screen = OptionScreen.unwrap(minecraft.screen);

View File

@@ -9,6 +9,10 @@ import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.common.ServerCommonPacketListener;
import net.minecraft.sounds.SoundEvent;
import javax.annotation.Nullable;
@@ -18,11 +22,12 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
}
/**
* Send a network message to the server.
* Convert a serverbound {@link NetworkMessage} to a Minecraft {@link Packet}.
*
* @param message The message to send.
* @param message The messsge to convert.
* @return The converted message.
*/
void sendToServer(NetworkMessage<ServerNetworkContext> message);
Packet<ServerCommonPacketListener> createPacket(NetworkMessage<ServerNetworkContext> message);
/**
* Render a {@link BakedModel}, using any loader-specific hooks.
@@ -35,4 +40,13 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
* @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);
/**
* Play a record at a particular position.
*
* @param pos The position to play this record.
* @param sound The record to play, or {@code null} to stop it.
* @see net.minecraft.client.renderer.LevelRenderer#playStreamingMusic(SoundEvent, BlockPos)
*/
void playStreamingMusic(BlockPos pos, @Nullable SoundEvent sound);
}

View File

@@ -125,10 +125,10 @@ public class SpriteRenderer {
}
public static float u(TextureAtlasSprite sprite, int x, int width) {
return sprite.getU((double) x / width * 16);
return sprite.getU((float) x / width);
}
public static float v(TextureAtlasSprite sprite, int y, int height) {
return sprite.getV((double) y / height * 16);
return sprite.getV((float) y / height);
}
}

View File

@@ -9,6 +9,7 @@ import com.mojang.blaze3d.platform.MemoryTracker;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import com.mojang.math.Axis;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.integration.ShaderMod;
import dan200.computercraft.client.render.RenderTypes;
@@ -25,6 +26,7 @@ import dan200.computercraft.shared.util.DirectionUtil;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.world.phys.AABB;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
@@ -255,6 +257,11 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
return Config.monitorDistance;
}
@ForgeOverride
public AABB getRenderBoundingBox(MonitorBlockEntity monitor) {
return monitor.getRenderBoundingBox();
}
/**
* Determine if any monitors were rendered this frame.
*

View File

@@ -6,7 +6,7 @@ package dan200.computercraft.client.sound;
import com.mojang.blaze3d.audio.Channel;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import io.netty.buffer.ByteBuf;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.client.sounds.AudioStream;
import net.minecraft.client.sounds.SoundEngine;
import org.lwjgl.BufferUtils;
@@ -36,7 +36,7 @@ class DfpwmStream implements AudioStream {
/**
* The {@link Channel} which this sound is playing on.
*
* @see SpeakerInstance#pushAudio(ByteBuf)
* @see SpeakerInstance#playAudio(SpeakerPosition, float, ByteBuffer)
*/
@Nullable
Channel channel;
@@ -44,7 +44,7 @@ class DfpwmStream implements AudioStream {
/**
* The underlying {@link SoundEngine} executor.
*
* @see SpeakerInstance#pushAudio(ByteBuf)
* @see SpeakerInstance#playAudio(SpeakerPosition, float, ByteBuffer)
* @see SoundEngine#executor
*/
@Nullable
@@ -58,12 +58,12 @@ class DfpwmStream implements AudioStream {
DfpwmStream() {
}
void push(ByteBuf input) {
var readable = input.readableBytes();
void push(ByteBuffer input) {
var readable = input.remaining();
var output = ByteBuffer.allocate(readable * 8).order(ByteOrder.nativeOrder());
for (var i = 0; i < readable; i++) {
var inputByte = input.readByte();
var inputByte = input.get();
for (var j = 0; j < 8; j++) {
var currentBit = (inputByte & 1) != 0;
var target = currentBit ? 127 : -128;

View File

@@ -7,11 +7,11 @@ package dan200.computercraft.client.sound;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import io.netty.buffer.ByteBuf;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
/**
* An instance of a speaker, which is either playing a {@link DfpwmStream} stream or a normal sound.
@@ -25,7 +25,7 @@ public class SpeakerInstance {
SpeakerInstance() {
}
public synchronized void pushAudio(ByteBuf buffer) {
private void pushAudio(ByteBuffer buffer) {
var sound = this.sound;
var stream = currentStream;
@@ -43,7 +43,9 @@ public class SpeakerInstance {
}
}
public void playAudio(SpeakerPosition position, float volume) {
public void playAudio(SpeakerPosition position, float volume, ByteBuffer buffer) {
pushAudio(buffer);
var soundManager = Minecraft.getInstance().getSoundManager();
if (sound != null && sound.stream != currentStream) {

View File

@@ -10,6 +10,7 @@ import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
@@ -40,7 +41,7 @@ public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
}
@Override
public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
var active = false;
if (turtle != null) {
var turtleNBT = turtle.getUpgradeNBTData(side);

View File

@@ -10,7 +10,8 @@ import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.RegistryHelper;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.impl.UpgradeManager;
import net.minecraft.client.Minecraft;
@@ -24,14 +25,13 @@ import java.util.stream.Stream;
/**
* A registry of {@link TurtleUpgradeModeller}s.
*
* @see dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller)
*/
public final class TurtleUpgradeModellers {
private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side) ->
private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side, data) ->
new TransformedModel(Minecraft.getInstance().getModelManager().getMissingModel(), Transformation.identity());
private static final Map<TurtleUpgradeSerialiser<?>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
private static final Map<UpgradeSerialiser<? extends ITurtleUpgrade>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
private static volatile boolean fetchedModels;
/**
* In order to avoid a double lookup of {@link ITurtleUpgrade} to {@link UpgradeManager.UpgradeWrapper} to
@@ -44,26 +44,29 @@ public final class TurtleUpgradeModellers {
private TurtleUpgradeModellers() {
}
public static <T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
synchronized (turtleModels) {
if (turtleModels.containsKey(serialiser)) {
throw new IllegalStateException("Modeller already registered for serialiser");
}
public static <T extends ITurtleUpgrade> void register(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
if (fetchedModels) {
throw new IllegalStateException(String.format(
"Turtle upgrade serialiser %s must be registered before models are baked.",
RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.serialiserRegistryKey()), serialiser)
));
}
turtleModels.put(serialiser, modeller);
if (turtleModels.putIfAbsent(serialiser, modeller) != null) {
throw new IllegalStateException("Modeller already registered for serialiser");
}
}
public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) {
@SuppressWarnings("unchecked")
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, access, side);
return modeller.getModel(upgrade, access, side, access.getUpgradeNBTData(side));
}
public static TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
@SuppressWarnings("unchecked")
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, data, side);
return modeller.getModel(upgrade, null, side, data);
}
private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {
@@ -75,6 +78,7 @@ public final class TurtleUpgradeModellers {
}
public static Stream<ResourceLocation> getDependencies() {
fetchedModels = true;
return turtleModels.values().stream().flatMap(x -> x.getDependencies().stream());
}
}

View File

@@ -1,13 +0,0 @@
{
"required": true,
"package": "dan200.computercraft.mixin.client",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_17",
"injectors": {
"defaultRequire": 1
},
"client": [
"ClientPacketListenerMixin"
],
"refmap": "client-computercraft.refmap.json"
}

View File

@@ -17,13 +17,12 @@ import dan200.computercraft.shared.computer.metrics.basic.Aggregate;
import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
import dan200.computercraft.shared.config.ConfigFile;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.platform.RegistryWrappers;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import java.util.HashMap;
import java.util.Map;
@@ -272,12 +271,12 @@ public final class LanguageProvider implements DataProvider {
private Stream<String> getExpectedKeys() {
return Stream.of(
RegistryWrappers.BLOCKS.stream()
.filter(x -> RegistryWrappers.BLOCKS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
.map(Block::getDescriptionId),
RegistryWrappers.ITEMS.stream()
.filter(x -> RegistryWrappers.ITEMS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
.map(Item::getDescriptionId),
BuiltInRegistries.BLOCK.holders()
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
.map(x -> x.value().getDescriptionId()),
BuiltInRegistries.ITEM.holders()
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
.map(x -> x.value().getDescriptionId()),
turtleUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),

View File

@@ -5,8 +5,9 @@
package dan200.computercraft.data;
import com.google.gson.JsonElement;
import dan200.computercraft.shared.platform.RegistryWrappers;
import dan200.computercraft.impl.RegistryHelper;
import net.minecraft.Util;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
@@ -67,7 +68,7 @@ public class ModelProvider implements DataProvider {
blocks.accept(new BlockModelGenerators(addBlockState, addModel, explicitItems::add));
items.accept(new ItemModelGenerators(addModel));
for (var block : RegistryWrappers.BLOCKS) {
for (var block : BuiltInRegistries.BLOCK) {
if (!blockStates.containsKey(block)) continue;
var item = Item.BY_BLOCK.get(block);
@@ -80,7 +81,7 @@ public class ModelProvider implements DataProvider {
}
List<CompletableFuture<?>> futures = new ArrayList<>();
saveCollection(output, futures, blockStates, x -> blockStatePath.json(RegistryWrappers.BLOCKS.getKey(x)));
saveCollection(output, futures, blockStates, x -> blockStatePath.json(RegistryHelper.getKeyOrThrow(BuiltInRegistries.BLOCK, x)));
saveCollection(output, futures, models, modelPath::json);
return Util.sequenceFailFast(futures);
}

View File

@@ -5,8 +5,9 @@
package dan200.computercraft.data;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
@@ -21,7 +22,7 @@ class PocketUpgradeProvider extends PocketUpgradeDataProvider {
}
@Override
protected void addUpgrades(Consumer<Upgrade<PocketUpgradeSerialiser<?>>> addUpgrade) {
protected void addUpgrades(Consumer<Upgrade<UpgradeSerialiser<? extends IPocketUpgrade>>> addUpgrade) {
addUpgrade.accept(simpleWithCustomItem(id("speaker"), PocketUpgradeSerialisers.SPEAKER.get(), Items.SPEAKER.get()));
simpleWithCustomItem(id("wireless_modem_normal"), PocketUpgradeSerialisers.WIRELESS_MODEM_NORMAL.get(), Items.WIRELESS_MODEM_NORMAL.get()).add(addUpgrade);
simpleWithCustomItem(id("wireless_modem_advanced"), PocketUpgradeSerialisers.WIRELESS_MODEM_ADVANCED.get(), Items.WIRELESS_MODEM_ADVANCED.get()).add(addUpgrade);

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
/**
* Wraps an existing {@link DataProvider}, passing generated JSON through {@link PrettyJsonWriter}.
*
* @param provider The provider to wrap.
* @param <T> The type of the provider to wrap.
*/
public record PrettyDataProvider<T extends DataProvider>(T provider) implements DataProvider {
@Override
public CompletableFuture<?> run(CachedOutput cachedOutput) {
return provider.run(new Output(cachedOutput));
}
@Override
public String getName() {
return provider.getName();
}
private record Output(CachedOutput output) implements CachedOutput {
@SuppressWarnings("deprecation")
private static final HashFunction HASH_FUNCTION = Hashing.sha1();
@Override
public void writeIfNeeded(Path path, byte[] bytes, HashCode hashCode) throws IOException {
if (path.getFileName().toString().endsWith(".json")) {
bytes = PrettyJsonWriter.reformat(bytes);
hashCode = HASH_FUNCTION.hashBytes(bytes);
}
output.writeIfNeeded(path, bytes, hashCode);
}
}
}

View File

@@ -22,11 +22,10 @@ import java.util.List;
/**
* Alternative version of {@link JsonWriter} which attempts to lay out the JSON in a more compact format.
* <p>
* Yes, this is at least a little deranged.
*
* @see PrettyDataProvider
*/
public class PrettyJsonWriter extends JsonWriter {
public static final boolean ENABLED = System.getProperty("cct.pretty-json") != null;
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
private static final int MAX_WIDTH = 120;
@@ -44,17 +43,6 @@ public class PrettyJsonWriter extends JsonWriter {
this.out = out;
}
/**
* Create a JSON writer. This will either be a pretty or normal version, depending on whether the global flag is
* set.
*
* @param out The writer to emit to.
* @return The constructed JSON writer.
*/
public static JsonWriter createWriter(Writer out) {
return ENABLED ? new PrettyJsonWriter(out) : new JsonWriter(out);
}
/**
* Reformat a JSON string with our pretty printer.
*
@@ -62,8 +50,6 @@ public class PrettyJsonWriter extends JsonWriter {
* @return The reformatted string.
*/
public static byte[] reformat(byte[] contents) {
if (!ENABLED) return contents;
JsonElement object;
try (var reader = new InputStreamReader(new ByteArrayInputStream(contents), StandardCharsets.UTF_8)) {
object = GSON.fromJson(reader, JsonElement.class);

View File

@@ -5,34 +5,56 @@
package dan200.computercraft.data;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.mojang.authlib.GameProfile;
import com.mojang.serialization.JsonOps;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.data.recipe.ShapedSpecBuilder;
import dan200.computercraft.data.recipe.ShapelessSpecBuilder;
import dan200.computercraft.impl.RegistryHelper;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.common.ClearColourRecipe;
import dan200.computercraft.shared.common.ColourableRecipe;
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.media.recipes.DiskRecipe;
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RecipeIngredients;
import dan200.computercraft.shared.platform.RegistryWrappers;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
import dan200.computercraft.shared.recipe.CustomShapelessRecipe;
import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
import dan200.computercraft.shared.turtle.items.TurtleItem;
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.util.ColourUtils;
import net.minecraft.Util;
import net.minecraft.advancements.Criterion;
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
import net.minecraft.advancements.critereon.ItemPredicate;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.PackOutput;
import net.minecraft.data.recipes.*;
import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.RecipeOutput;
import net.minecraft.data.recipes.ShapedRecipeBuilder;
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.*;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.ShapedRecipe;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.Blocks;
@@ -43,7 +65,7 @@ import java.util.function.Consumer;
import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER;
import static dan200.computercraft.api.ComputerCraftTags.Items.WIRED_MODEM;
class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
private final RecipeIngredients ingredients = PlatformHelper.get().getRecipeIngredients();
private final TurtleUpgradeDataProvider turtleUpgrades;
private final PocketUpgradeDataProvider pocketUpgrades;
@@ -55,40 +77,37 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
@Override
public void buildRecipes(Consumer<FinishedRecipe> add) {
public void buildRecipes(RecipeOutput add) {
basicRecipes(add);
diskColours(add);
pocketUpgrades(add);
turtleUpgrades(add);
turtleOverlays(add);
addSpecial(add, ModRegistry.RecipeSerializers.PRINTOUT.get());
addSpecial(add, ModRegistry.RecipeSerializers.DISK.get());
addSpecial(add, ModRegistry.RecipeSerializers.DYEABLE_ITEM.get());
addSpecial(add, ModRegistry.RecipeSerializers.DYEABLE_ITEM_CLEAR.get());
addSpecial(add, ModRegistry.RecipeSerializers.TURTLE_UPGRADE.get());
addSpecial(add, ModRegistry.RecipeSerializers.POCKET_COMPUTER_UPGRADE.get());
addSpecial(add, new PrintoutRecipe(CraftingBookCategory.MISC));
addSpecial(add, new DiskRecipe(CraftingBookCategory.MISC));
addSpecial(add, new ColourableRecipe(CraftingBookCategory.MISC));
addSpecial(add, new ClearColourRecipe(CraftingBookCategory.MISC));
addSpecial(add, new TurtleUpgradeRecipe(CraftingBookCategory.MISC));
addSpecial(add, new PocketComputerUpgradeRecipe(CraftingBookCategory.MISC));
}
/**
* Register a crafting recipe for a disk of every dye colour.
*
* @param add The callback to add recipes.
* @param output The callback to add recipes.
*/
private void diskColours(Consumer<FinishedRecipe> add) {
private void diskColours(RecipeOutput output) {
for (var colour : Colour.VALUES) {
ShapelessRecipeBuilder
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.DISK.get())
ShapelessSpecBuilder
.shapeless(RecipeCategory.REDSTONE, DiskItem.createFromIDAndColour(-1, null, colour.getHex()))
.requires(ingredients.redstone())
.requires(Items.PAPER)
.requires(DyeItem.byColor(ofColour(colour)))
.group("computercraft:disk")
.unlockedBy("has_drive", inventoryChange(ModRegistry.Blocks.DISK_DRIVE.get()))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(), add)
.withResultTag(x -> x.putInt(IColouredItem.NBT_COLOUR, colour.getHex())),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "disk_" + (colour.ordinal() + 1))
);
.build(ImpostorShapelessRecipe::new)
.save(output, new ResourceLocation(ComputerCraftAPI.MOD_ID, "disk_" + (colour.ordinal() + 1)));
}
}
@@ -101,23 +120,22 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
*
* @param add The callback to add recipes.
*/
private void turtleUpgrades(Consumer<FinishedRecipe> add) {
private void turtleUpgrades(RecipeOutput add) {
for (var turtleItem : turtleItems()) {
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
var name = RegistryWrappers.ITEMS.getKey(turtleItem);
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, result.getItem())
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null))
.group(name.toString())
.pattern("#T")
.define('T', base.getItem())
.define('#', upgrade.getCraftingItem().getItem())
.unlockedBy("has_items",
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.unlockedBy("has_items", inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.build(ImpostorShapedRecipe::new)
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
add,
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
);
}
@@ -133,31 +151,30 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
*
* @param add The callback to add recipes.
*/
private void pocketUpgrades(Consumer<FinishedRecipe> add) {
private void pocketUpgrades(RecipeOutput add) {
for (var pocket : pocketComputerItems()) {
var base = pocket.create(-1, null, -1, null);
var name = RegistryWrappers.ITEMS.getKey(pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, result.getItem())
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade)))
.group(name.toString())
.pattern("#")
.pattern("P")
.define('P', base.getItem())
.define('#', upgrade.getCraftingItem().getItem())
.unlockedBy("has_items",
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.unlockedBy("has_items", inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.build(ImpostorShapedRecipe::new)
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
add,
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
);
}
}
}
private void turtleOverlays(Consumer<FinishedRecipe> add) {
private void turtleOverlays(RecipeOutput add) {
turtleOverlay(add, "turtle_trans_overlay", x -> x
.unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
.requires(ColourUtils.getDyeTag(DyeColor.LIGHT_BLUE))
@@ -178,28 +195,24 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
);
}
private void turtleOverlay(Consumer<FinishedRecipe> add, String overlay, Consumer<ShapelessRecipeBuilder> build) {
private void turtleOverlay(RecipeOutput add, String overlay, Consumer<ShapelessSpecBuilder> build) {
for (var turtleItem : turtleItems()) {
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
var name = RegistryWrappers.ITEMS.getKey(turtleItem);
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
var builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.REDSTONE, base.getItem())
var builder = ShapelessSpecBuilder.shapeless(RecipeCategory.REDSTONE, base)
.group(name.withSuffix("_overlay").toString())
.unlockedBy("has_turtle", inventoryChange(base.getItem()));
build.accept(builder);
builder
.requires(base.getItem())
.save(
RecipeWrapper
.wrap(ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get(), add)
.withExtraData(x -> x.addProperty("overlay", new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay).toString())),
name.withSuffix("_overlays/" + overlay)
);
.build(s -> new TurtleOverlayRecipe(s, new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay)))
.save(add, name.withSuffix("_overlays/" + overlay));
}
}
private void basicRecipes(Consumer<FinishedRecipe> add) {
private void basicRecipes(RecipeOutput add) {
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.CABLE.get(), 6)
.pattern(" # ")
@@ -233,7 +246,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.unlockedBy("has_components", inventoryChange(itemPredicate(ingredients.redstone()), itemPredicate(ingredients.goldIngot())))
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_ADVANCED.get())
.pattern("###")
.pattern("#C#")
@@ -241,10 +254,8 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('#', ingredients.goldIngot())
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade")
);
.build(ComputerUpgradeRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade"));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.COMPUTER_COMMAND.get())
@@ -257,7 +268,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.unlockedBy("has_components", inventoryChange(Blocks.COMMAND_BLOCK))
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_NORMAL.get())
.pattern("###")
.pattern("#C#")
@@ -266,9 +277,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
.define('I', ingredients.woodenChest())
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
.buildOrThrow(TurtleRecipe::of)
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
.pattern("###")
.pattern("#C#")
@@ -277,9 +289,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
.define('I', ingredients.woodenChest())
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
.buildOrThrow(TurtleRecipe::of)
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
.pattern("###")
.pattern("#C#")
@@ -288,10 +301,8 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('C', ModRegistry.Items.TURTLE_NORMAL.get())
.define('B', ingredients.goldBlock())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade")
);
.build(ComputerUpgradeRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade"));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.DISK_DRIVE.get())
@@ -347,7 +358,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.unlockedBy("has_apple", inventoryChange(Items.GOLDEN_APPLE))
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get())
.pattern("###")
.pattern("#C#")
@@ -355,10 +366,8 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('#', ingredients.goldIngot())
.define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade")
);
.build(ComputerUpgradeRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade"));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.PRINTER.get())
@@ -425,57 +434,53 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.unlockedBy("has_wireless", inventoryChange(ModRegistry.Blocks.WIRELESS_MODEM_NORMAL.get()))
.save(add);
ShapelessRecipeBuilder
.shapeless(RecipeCategory.DECORATIONS, Items.PLAYER_HEAD)
ShapelessSpecBuilder
.shapeless(RecipeCategory.DECORATIONS, playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c"))
.requires(ingredients.head())
.requires(ModRegistry.Items.MONITOR_NORMAL.get())
.unlockedBy("has_monitor", inventoryChange(ModRegistry.Items.MONITOR_NORMAL.get()))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
.withResultTag(playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c")),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy")
);
.build(CustomShapelessRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy"));
ShapelessRecipeBuilder
.shapeless(RecipeCategory.DECORATIONS, Items.PLAYER_HEAD)
ShapelessSpecBuilder
.shapeless(RecipeCategory.DECORATIONS, playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb"))
.requires(ingredients.head())
.requires(ModRegistry.Items.COMPUTER_ADVANCED.get())
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_ADVANCED.get()))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
.withResultTag(playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb")),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200")
);
.build(CustomShapelessRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200"));
ShapelessRecipeBuilder
ShapelessSpecBuilder
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_PAGES.get())
.requires(ModRegistry.Items.PRINTED_PAGE.get(), 2)
.requires(ingredients.string())
.unlockedBy("has_printer", inventoryChange(ModRegistry.Blocks.PRINTER.get()))
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(), add));
.build(ImpostorShapelessRecipe::new)
.save(add);
ShapelessRecipeBuilder
ShapelessSpecBuilder
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_BOOK.get())
.requires(ingredients.leather())
.requires(ModRegistry.Items.PRINTED_PAGE.get(), 1)
.requires(ingredients.string())
.unlockedBy("has_printer", inventoryChange(ModRegistry.Blocks.PRINTER.get()))
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(), add));
.build(ImpostorShapelessRecipe::new)
.save(add);
}
private static DyeColor ofColour(Colour colour) {
return DyeColor.byId(15 - colour.ordinal());
}
private static InventoryChangeTrigger.TriggerInstance inventoryChange(TagKey<Item> stack) {
private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(TagKey<Item> stack) {
return InventoryChangeTrigger.TriggerInstance.hasItems(itemPredicate(stack));
}
private static InventoryChangeTrigger.TriggerInstance inventoryChange(ItemLike... stack) {
private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(ItemLike... stack) {
return InventoryChangeTrigger.TriggerInstance.hasItems(stack);
}
private static InventoryChangeTrigger.TriggerInstance inventoryChange(ItemPredicate... items) {
private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(ItemPredicate... items) {
return InventoryChangeTrigger.TriggerInstance.hasItems(items);
}
@@ -488,11 +493,12 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
private static ItemPredicate itemPredicate(Ingredient ingredient) {
var json = ingredient.toJson();
var json = Util.getOrThrow(Ingredient.CODEC_NONEMPTY.encodeStart(JsonOps.INSTANCE, ingredient), JsonParseException::new);
if (!(json instanceof JsonObject object)) throw new IllegalStateException("Unknown ingredient " + json);
if (object.has("item")) {
return itemPredicate(ShapedRecipe.itemFromJson(object));
var item = Util.getOrThrow(ItemStack.ITEM_WITH_COUNT_CODEC.parse(JsonOps.INSTANCE, object), JsonParseException::new);
return itemPredicate(item.getItem());
} else if (object.has("tag")) {
return itemPredicate(TagKey.create(Registries.ITEM, new ResourceLocation(GsonHelper.getAsString(object, "tag"))));
} else {
@@ -500,15 +506,14 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
}
private static CompoundTag playerHead(String name, String uuid) {
private static ItemStack playerHead(String name, String uuid) {
var item = new ItemStack(Items.PLAYER_HEAD);
var owner = NbtUtils.writeGameProfile(new CompoundTag(), new GameProfile(UUID.fromString(uuid), name));
var tag = new CompoundTag();
tag.put(PlayerHeadItem.TAG_SKULL_OWNER, owner);
return tag;
item.getOrCreateTag().put(PlayerHeadItem.TAG_SKULL_OWNER, owner);
return item;
}
private static void addSpecial(Consumer<FinishedRecipe> add, SimpleCraftingRecipeSerializer<?> special) {
SpecialRecipeBuilder.special(special).save(add, RegistryWrappers.RECIPE_SERIALIZERS.getKey(special).toString());
private static void addSpecial(RecipeOutput add, Recipe<?> recipe) {
add.accept(RegistryHelper.getKeyOrThrow(BuiltInRegistries.RECIPE_SERIALIZER, recipe.getSerializer()), recipe, null);
}
}

View File

@@ -1,93 +0,0 @@
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data;
import com.google.gson.JsonObject;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.crafting.RecipeSerializer;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* Adapter for recipes which overrides the serializer and adds custom item NBT.
*/
final class RecipeWrapper implements Consumer<FinishedRecipe> {
private final Consumer<FinishedRecipe> add;
private final RecipeSerializer<?> serializer;
private final List<Consumer<JsonObject>> extend = new ArrayList<>(0);
RecipeWrapper(Consumer<FinishedRecipe> add, RecipeSerializer<?> serializer) {
this.add = add;
this.serializer = serializer;
}
public static RecipeWrapper wrap(RecipeSerializer<?> serializer, Consumer<FinishedRecipe> original) {
return new RecipeWrapper(original, serializer);
}
public RecipeWrapper withExtraData(Consumer<JsonObject> extra) {
extend.add(extra);
return this;
}
public RecipeWrapper withResultTag(@Nullable CompoundTag resultTag) {
if (resultTag == null) return this;
extend.add(json -> {
var object = GsonHelper.getAsJsonObject(json, "result");
object.addProperty("nbt", resultTag.toString());
});
return this;
}
public RecipeWrapper withResultTag(Consumer<CompoundTag> resultTag) {
var tag = new CompoundTag();
resultTag.accept(tag);
return withResultTag(tag);
}
@Override
public void accept(FinishedRecipe finishedRecipe) {
add.accept(new RecipeImpl(finishedRecipe, serializer, extend));
}
private record RecipeImpl(
FinishedRecipe recipe, RecipeSerializer<?> serializer, List<Consumer<JsonObject>> extend
) implements FinishedRecipe {
@Override
public void serializeRecipeData(JsonObject jsonObject) {
recipe.serializeRecipeData(jsonObject);
for (var extender : extend) extender.accept(jsonObject);
}
@Override
public ResourceLocation getId() {
return recipe.getId();
}
@Override
public RecipeSerializer<?> getType() {
return serializer;
}
@Nullable
@Override
public JsonObject serializeAdvancement() {
return recipe.serializeAdvancement();
}
@Nullable
@Override
public ResourceLocation getAdvancementId() {
return recipe.getAdvancementId();
}
}
}

View File

@@ -5,8 +5,9 @@
package dan200.computercraft.data;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.impl.RegistryHelper;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.platform.RegistryWrappers;
import net.minecraft.core.Registry;
import net.minecraft.data.tags.ItemTagsProvider;
import net.minecraft.data.tags.TagsProvider;
import net.minecraft.tags.BlockTags;
@@ -111,9 +112,9 @@ class TagProvider {
TagAppender<T> tag(TagKey<T> tag);
}
public record TagAppender<T>(RegistryWrappers.RegistryWrapper<T> registry, TagBuilder builder) {
public record TagAppender<T>(Registry<T> registry, TagBuilder builder) {
public TagAppender<T> add(T object) {
builder.addElement(registry.getKey(object));
builder.addElement(RegistryHelper.getKeyOrThrow(registry, object));
return this;
}

View File

@@ -6,8 +6,9 @@ package dan200.computercraft.data;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftTags.Blocks;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
@@ -22,7 +23,7 @@ class TurtleUpgradeProvider extends TurtleUpgradeDataProvider {
}
@Override
protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
protected void addUpgrades(Consumer<Upgrade<UpgradeSerialiser<? extends ITurtleUpgrade>>> addUpgrade) {
simpleWithCustomItem(id("speaker"), TurtleSerialisers.SPEAKER.get(), Items.SPEAKER.get()).add(addUpgrade);
simpleWithCustomItem(vanilla("crafting_table"), TurtleSerialisers.WORKBENCH.get(), net.minecraft.world.item.Items.CRAFTING_TABLE).add(addUpgrade);
simpleWithCustomItem(id("wireless_modem_normal"), TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), Items.WIRELESS_MODEM_NORMAL.get()).add(addUpgrade);

View File

@@ -0,0 +1,129 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.recipe;
import com.mojang.serialization.DataResult;
import dan200.computercraft.shared.recipe.RecipeProperties;
import net.minecraft.Util;
import net.minecraft.advancements.AdvancementRequirements;
import net.minecraft.advancements.AdvancementRewards;
import net.minecraft.advancements.Criterion;
import net.minecraft.advancements.critereon.RecipeUnlockedTrigger;
import net.minecraft.data.recipes.RecipeBuilder;
import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.RecipeOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
/**
* An abstract base class for creating recipes, in the style of {@link RecipeBuilder}.
*
* @param <S> The type of this class.
* @param <O> The output of this builder.
* @see ShapelessSpecBuilder
*/
public abstract class AbstractRecipeBuilder<S extends AbstractRecipeBuilder<S, O>, O> {
private final RecipeCategory category;
protected final ItemStack result;
private String group = "";
private final Map<String, Criterion<?>> criteria = new LinkedHashMap<>();
protected AbstractRecipeBuilder(RecipeCategory category, ItemStack result) {
this.category = category;
this.result = result;
}
/**
* Set the group for this recipe.
*
* @param group The new group.
* @return This object, for chaining.
*/
public final S group(String group) {
this.group = group;
return self();
}
/**
* Add a criterion to this recipe.
*
* @param name The name of the criterion.
* @param criterion The criterion to add.
* @return This object, for chaining.
*/
public final S unlockedBy(String name, Criterion<?> criterion) {
criteria.put(name, criterion);
return self();
}
/**
* Convert this builder into the output ({@link O}) object.
*
* @param properties The properties for this recipe.
* @return The built object.
*/
protected abstract O build(RecipeProperties properties);
/**
* Convert this builder into a concrete recipe.
*
* @param factory The recipe's constructor.
* @return The "built" recipe.
*/
public final FinishedRecipe build(Function<O, Recipe<?>> factory) {
var properties = new RecipeProperties(group, RecipeBuilder.determineBookCategory(category), true);
return new FinishedRecipe(factory.apply(build(properties)), result.getItem(), category, criteria);
}
/**
* Convert this builder into a concrete recipe.
*
* @param factory The recipe's constructor.
* @return The "built" recipe.
*/
public final FinishedRecipe buildOrThrow(Function<O, DataResult<? extends Recipe<?>>> factory) {
return build(s -> Util.getOrThrow(factory.apply(s), IllegalStateException::new));
}
@SuppressWarnings("unchecked")
private S self() {
return (S) this;
}
public static final class FinishedRecipe {
private final Recipe<?> recipe;
private final Item result;
private final RecipeCategory category;
private final Map<String, Criterion<?>> criteria;
private FinishedRecipe(Recipe<?> recipe, Item result, RecipeCategory category, Map<String, Criterion<?>> criteria) {
this.recipe = recipe;
this.result = result;
this.category = category;
this.criteria = criteria;
}
public void save(RecipeOutput output, ResourceLocation id) {
if (criteria.isEmpty()) throw new IllegalStateException("No way of obtaining recipe " + id);
var advancement = output.advancement()
.addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(id))
.rewards(AdvancementRewards.Builder.recipe(id))
.requirements(AdvancementRequirements.Strategy.OR);
for (var entry : criteria.entrySet()) advancement.addCriterion(entry.getKey(), entry.getValue());
output.accept(id, recipe, advancement.build(id.withPrefix("recipes/" + category.getFolderName() + "/")));
}
public void save(RecipeOutput output) {
save(output, RecipeBuilder.getDefaultRecipeId(result));
}
}
}

View File

@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.recipe;
import dan200.computercraft.shared.recipe.RecipeProperties;
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.ShapedRecipeBuilder;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.ShapedRecipePattern;
import net.minecraft.world.level.ItemLike;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A builder for {@link ShapedRecipeSpec}s, much like {@link ShapedRecipeBuilder}.
*/
public final class ShapedSpecBuilder extends AbstractRecipeBuilder<ShapedSpecBuilder, ShapedRecipeSpec> {
private final List<String> rows = new ArrayList<>();
private final Map<Character, Ingredient> key = new LinkedHashMap<>();
private ShapedSpecBuilder(RecipeCategory category, ItemStack result) {
super(category, result);
}
public static ShapedSpecBuilder shaped(RecipeCategory category, ItemStack result) {
return new ShapedSpecBuilder(category, result);
}
public static ShapedSpecBuilder shaped(RecipeCategory category, ItemLike result) {
return new ShapedSpecBuilder(category, new ItemStack(result));
}
public ShapedSpecBuilder define(char key, Ingredient ingredient) {
if (this.key.containsKey(key)) throw new IllegalArgumentException("Symbol '" + key + "' is already defined!");
if (key == ' ') throw new IllegalArgumentException("Symbol ' ' (whitespace) is reserved and cannot be defined");
this.key.put(key, ingredient);
return this;
}
public ShapedSpecBuilder define(char key, TagKey<Item> tag) {
return this.define(key, Ingredient.of(tag));
}
public ShapedSpecBuilder define(char key, ItemLike item) {
return this.define(key, Ingredient.of(item));
}
public ShapedSpecBuilder pattern(String pattern) {
if (!this.rows.isEmpty() && pattern.length() != this.rows.get(0).length()) {
throw new IllegalArgumentException("Pattern must be the same width on every line!");
} else {
this.rows.add(pattern);
return this;
}
}
@Override
protected ShapedRecipeSpec build(RecipeProperties properties) {
return new ShapedRecipeSpec(properties, ShapedRecipePattern.of(key, rows), result);
}
}

View File

@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.recipe;
import dan200.computercraft.shared.recipe.RecipeProperties;
import dan200.computercraft.shared.recipe.ShapelessRecipeSpec;
import net.minecraft.core.NonNullList;
import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.ItemLike;
/**
* A builder for {@link ShapelessRecipeSpec}s, much like {@link ShapelessRecipeBuilder}.
*/
public final class ShapelessSpecBuilder extends AbstractRecipeBuilder<ShapelessSpecBuilder, ShapelessRecipeSpec> {
private final NonNullList<Ingredient> ingredients = NonNullList.create();
private ShapelessSpecBuilder(RecipeCategory category, ItemStack result) {
super(category, result);
}
public static ShapelessSpecBuilder shapeless(RecipeCategory category, ItemStack result) {
return new ShapelessSpecBuilder(category, result);
}
public static ShapelessSpecBuilder shapeless(RecipeCategory category, ItemLike result) {
return new ShapelessSpecBuilder(category, new ItemStack(result));
}
public ShapelessSpecBuilder requires(Ingredient ingredient, int count) {
for (int i = 0; i < count; i++) ingredients.add(ingredient);
return this;
}
public ShapelessSpecBuilder requires(Ingredient ingredient) {
return requires(ingredient, 1);
}
public ShapelessSpecBuilder requires(ItemLike item) {
return requires(Ingredient.of(new ItemStack(item)));
}
public ShapelessSpecBuilder requires(ItemLike item, int count) {
return requires(Ingredient.of(new ItemStack(item)), count);
}
public ShapelessSpecBuilder requires(TagKey<Item> item) {
return requires(Ingredient.of(item));
}
@Override
protected ShapelessRecipeSpec build(RecipeProperties properties) {
return new ShapelessRecipeSpec(properties, ingredients, result);
}
}

View File

@@ -15,10 +15,11 @@ import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.core.filesystem.WritableFileMount;
import dan200.computercraft.impl.detail.DetailRegistryImpl;
import dan200.computercraft.impl.network.wired.WiredNodeImpl;
@@ -44,8 +45,8 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
private final DetailRegistry<ItemStack> itemStackDetails = new DetailRegistryImpl<>(ItemDetails::fillBasic);
private final DetailRegistry<BlockReference> blockDetails = new DetailRegistryImpl<>(BlockDetails::fillBasic);
protected static final ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
protected static final ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
protected static final ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
protected static final ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
public static @Nullable InputStream getResourceFile(MinecraftServer server, String domain, String subPath) {
var manager = server.getResourceManager();
@@ -116,12 +117,12 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
}
@Override
public final ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId() {
public final ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId() {
return turtleUpgradeRegistryId;
}
@Override
public final ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId() {
public final ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId() {
return pocketUpgradeRegistryId;
}

View File

@@ -9,27 +9,25 @@ import dan200.computercraft.shared.peripheral.generic.ComponentLookup;
import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.BlockEntity;
import javax.annotation.Nullable;
/**
* The registry for peripheral providers.
* <p>
* This lives in the {@code impl} package despite it not being part of the public API, in order to mirror Forge's class.
*/
public final class Peripherals {
private static final GenericPeripheralProvider<Runnable> genericProvider = new GenericPeripheralProvider<>();
private static final GenericPeripheralProvider genericProvider = new GenericPeripheralProvider();
private Peripherals() {
}
public static void addGenericLookup(ComponentLookup<? super Runnable> lookup) {
public static void addGenericLookup(ComponentLookup lookup) {
genericProvider.registerLookup(lookup);
}
public static @Nullable IPeripheral getGenericPeripheral(Level level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity, Runnable invalidate) {
return genericProvider.getPeripheral(level, pos, side, blockEntity, invalidate);
public static @Nullable IPeripheral getGenericPeripheral(ServerLevel level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity) {
return genericProvider.getPeripheral(level, pos, side, blockEntity);
}
}

View File

@@ -6,19 +6,18 @@ package dan200.computercraft.impl;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import java.util.stream.Stream;
public final class PocketUpgrades {
private static final UpgradeManager<PocketUpgradeSerialiser<?>, IPocketUpgrade> registry = new UpgradeManager<>(
"pocket computer upgrade", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.registryId()
private static final UpgradeManager<IPocketUpgrade> registry = new UpgradeManager<>(
"pocket computer upgrade", "computercraft/pocket_upgrades", IPocketUpgrade.serialiserRegistryKey()
);
private PocketUpgrades() {
}
public static UpgradeManager<PocketUpgradeSerialiser<?>, IPocketUpgrade> instance() {
public static UpgradeManager<IPocketUpgrade> instance() {
return registry;
}

View File

@@ -6,19 +6,18 @@ package dan200.computercraft.impl;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import java.util.stream.Stream;
public final class TurtleUpgrades {
private static final UpgradeManager<TurtleUpgradeSerialiser<?>, ITurtleUpgrade> registry = new UpgradeManager<>(
"turtle upgrade", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.registryId()
private static final UpgradeManager<ITurtleUpgrade> registry = new UpgradeManager<>(
"turtle upgrade", "computercraft/turtle_upgrades", ITurtleUpgrade.serialiserRegistryKey()
);
private TurtleUpgrades() {
}
public static UpgradeManager<TurtleUpgradeSerialiser<?>, ITurtleUpgrade> instance() {
public static UpgradeManager<ITurtleUpgrade> instance() {
return registry;
}

View File

@@ -31,27 +31,26 @@ import java.util.stream.Collectors;
/**
* Manages turtle and pocket computer upgrades.
*
* @param <R> The type of upgrade serialisers.
* @param <T> The type of upgrade.
* @see TurtleUpgrades
* @see PocketUpgrades
*/
public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
public class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeManager.class);
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
public record UpgradeWrapper<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase>(
String id, T upgrade, R serialiser, String modId
public record UpgradeWrapper<T extends UpgradeBase>(
String id, T upgrade, UpgradeSerialiser<? extends T> serialiser, String modId
) {
}
private final String kind;
private final ResourceKey<Registry<R>> registry;
private final ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry;
private Map<String, UpgradeWrapper<R, T>> current = Map.of();
private Map<T, UpgradeWrapper<R, T>> currentWrappers = Map.of();
private Map<String, UpgradeWrapper<T>> current = Map.of();
private Map<T, UpgradeWrapper<T>> currentWrappers = Map.of();
public UpgradeManager(String kind, String path, ResourceKey<Registry<R>> registry) {
public UpgradeManager(String kind, String path, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry) {
super(GSON, path);
this.kind = kind;
this.registry = registry;
@@ -64,7 +63,7 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
}
@Nullable
public UpgradeWrapper<R, T> getWrapper(T upgrade) {
public UpgradeWrapper<T> getWrapper(T upgrade) {
return currentWrappers.get(upgrade);
}
@@ -92,16 +91,17 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
return currentWrappers.keySet();
}
public Map<String, UpgradeWrapper<R, T>> getUpgradeWrappers() {
public Map<String, UpgradeWrapper<T>> getUpgradeWrappers() {
return current;
}
@Override
protected void apply(Map<ResourceLocation, JsonElement> upgrades, ResourceManager manager, ProfilerFiller profiler) {
Map<String, UpgradeWrapper<R, T>> newUpgrades = new HashMap<>();
var registry = RegistryHelper.getRegistry(this.registry);
Map<String, UpgradeWrapper<T>> newUpgrades = new HashMap<>();
for (var element : upgrades.entrySet()) {
try {
loadUpgrade(newUpgrades, element.getKey(), element.getValue());
loadUpgrade(registry, newUpgrades, element.getKey(), element.getValue());
} catch (IllegalArgumentException | JsonParseException e) {
LOG.error("Error loading {} {} from JSON file", kind, element.getKey(), e);
}
@@ -112,29 +112,29 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
LOG.info("Loaded {} {}s", current.size(), kind);
}
private void loadUpgrade(Map<String, UpgradeWrapper<R, T>> current, ResourceLocation id, JsonElement json) {
private void loadUpgrade(Registry<UpgradeSerialiser<? extends T>> registry, Map<String, UpgradeWrapper<T>> current, ResourceLocation id, JsonElement json) {
var root = GsonHelper.convertToJsonObject(json, "top element");
if (!PlatformHelper.get().shouldLoadResource(root)) return;
var serialiserId = new ResourceLocation(GsonHelper.getAsString(root, "type"));
var serialiser = PlatformHelper.get().tryGetRegistryObject(registry, serialiserId);
var serialiser = registry.get(serialiserId);
if (serialiser == null) throw new JsonSyntaxException("Unknown upgrade type '" + serialiserId + "'");
// TODO: Can we track which mod this resource came from and use that instead? It's theoretically possible,
// but maybe not ideal for datapacks.
var modId = id.getNamespace();
if (modId.equals("minecraft") || modId.equals("")) modId = ComputerCraftAPI.MOD_ID;
if (modId.equals("minecraft") || modId.isEmpty()) modId = ComputerCraftAPI.MOD_ID;
var upgrade = serialiser.fromJson(id, root);
if (!upgrade.getUpgradeID().equals(id)) {
throw new IllegalArgumentException("Upgrade " + id + " from " + serialiser + " was incorrectly given id " + upgrade.getUpgradeID());
}
var result = new UpgradeWrapper<R, T>(id.toString(), upgrade, serialiser, modId);
var result = new UpgradeWrapper<T>(id.toString(), upgrade, serialiser, modId);
current.put(result.id(), result);
}
public void loadFromNetwork(Map<String, UpgradeWrapper<R, T>> newUpgrades) {
public void loadFromNetwork(Map<String, UpgradeWrapper<T>> newUpgrades) {
current = Collections.unmodifiableMap(newUpgrades);
currentWrappers = newUpgrades.values().stream().collect(Collectors.toUnmodifiableMap(UpgradeWrapper::upgrade, x -> x));
}

View File

@@ -1,25 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin;
import dan200.computercraft.data.PrettyJsonWriter;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(targets = "net/minecraft/data/HashCache$CacheUpdater")
public class CacheUpdaterMixin {
@SuppressWarnings("UnusedMethod")
@ModifyArg(
method = "writeIfNeeded",
at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;write(Ljava/nio/file/Path;[B[Ljava/nio/file/OpenOption;)Ljava/nio/file/Path;"),
require = 0
)
private byte[] reformatJson(byte[] contents) {
// It would be cleaner to do this inside DataProvider.saveStable, but Forge's version of Mixin doesn't allow us
// to inject into interfaces.
return PrettyJsonWriter.reformat(contents);
}
}

View File

@@ -63,6 +63,12 @@ public final class CommonHooks {
ComputerMBean.start(server);
}
public static void onServerStarted(MinecraftServer server) {
// ItemDetails requires creative tabs to be populated, however by default this is done lazily on the client and
// not at all on the server! We instead do this once on server startup.
CreativeModeTabs.tryRebuildTabContents(server.getWorldData().enabledFeatures(), false, server.registryAccess());
}
public static void onServerStopped() {
resetState();
}

View File

@@ -5,13 +5,15 @@
package dan200.computercraft.shared;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.serialization.Codec;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.DetailProvider;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.TurtleUpgrades;
@@ -36,7 +38,6 @@ import dan200.computercraft.shared.computer.items.ComputerItem;
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.ConstantLootConditionSerializer;
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.details.BlockDetails;
@@ -263,29 +264,29 @@ public final class ModRegistry {
}
public static class TurtleSerialisers {
static final RegistrationHelper<TurtleUpgradeSerialiser<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(TurtleUpgradeSerialiser.registryId());
static final RegistrationHelper<UpgradeSerialiser<? extends ITurtleUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(ITurtleUpgrade.serialiserRegistryKey());
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> TurtleUpgradeSerialiser.simpleWithCustomItem(TurtleSpeaker::new));
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleCraftingTable>> WORKBENCH =
REGISTRY.register("workbench", () -> TurtleUpgradeSerialiser.simpleWithCustomItem(TurtleCraftingTable::new));
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_NORMAL =
REGISTRY.register("wireless_modem_normal", () -> TurtleUpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, false)));
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_ADVANCED =
REGISTRY.register("wireless_modem_advanced", () -> TurtleUpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, true)));
public static final RegistryEntry<UpgradeSerialiser<TurtleSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> UpgradeSerialiser.simpleWithCustomItem(TurtleSpeaker::new));
public static final RegistryEntry<UpgradeSerialiser<TurtleCraftingTable>> WORKBENCH =
REGISTRY.register("workbench", () -> UpgradeSerialiser.simpleWithCustomItem(TurtleCraftingTable::new));
public static final RegistryEntry<UpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_NORMAL =
REGISTRY.register("wireless_modem_normal", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, false)));
public static final RegistryEntry<UpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_ADVANCED =
REGISTRY.register("wireless_modem_advanced", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, true)));
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleTool>> TOOL = REGISTRY.register("tool", () -> TurtleToolSerialiser.INSTANCE);
public static final RegistryEntry<UpgradeSerialiser<TurtleTool>> TOOL = REGISTRY.register("tool", () -> TurtleToolSerialiser.INSTANCE);
}
public static class PocketUpgradeSerialisers {
static final RegistrationHelper<PocketUpgradeSerialiser<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(PocketUpgradeSerialiser.registryId());
static final RegistrationHelper<UpgradeSerialiser<? extends IPocketUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(IPocketUpgrade.serialiserRegistryKey());
public static final RegistryEntry<PocketUpgradeSerialiser<PocketSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> PocketUpgradeSerialiser.simpleWithCustomItem(PocketSpeaker::new));
public static final RegistryEntry<PocketUpgradeSerialiser<PocketModem>> WIRELESS_MODEM_NORMAL =
REGISTRY.register("wireless_modem_normal", () -> PocketUpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, false)));
public static final RegistryEntry<PocketUpgradeSerialiser<PocketModem>> WIRELESS_MODEM_ADVANCED =
REGISTRY.register("wireless_modem_advanced", () -> PocketUpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, true)));
public static final RegistryEntry<UpgradeSerialiser<PocketSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> UpgradeSerialiser.simpleWithCustomItem(PocketSpeaker::new));
public static final RegistryEntry<UpgradeSerialiser<PocketModem>> WIRELESS_MODEM_NORMAL =
REGISTRY.register("wireless_modem_normal", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, false)));
public static final RegistryEntry<UpgradeSerialiser<PocketModem>> WIRELESS_MODEM_ADVANCED =
REGISTRY.register("wireless_modem_advanced", () -> UpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, true)));
}
public static class Menus {
@@ -347,13 +348,13 @@ public final class ModRegistry {
static final RegistrationHelper<LootItemConditionType> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.LOOT_CONDITION_TYPE);
public static final RegistryEntry<LootItemConditionType> BLOCK_NAMED = REGISTRY.register("block_named",
() -> ConstantLootConditionSerializer.type(BlockNamedEntityLootCondition.INSTANCE));
() -> new LootItemConditionType(Codec.unit(BlockNamedEntityLootCondition.INSTANCE)));
public static final RegistryEntry<LootItemConditionType> PLAYER_CREATIVE = REGISTRY.register("player_creative",
() -> ConstantLootConditionSerializer.type(PlayerCreativeLootCondition.INSTANCE));
() -> new LootItemConditionType(Codec.unit(PlayerCreativeLootCondition.INSTANCE)));
public static final RegistryEntry<LootItemConditionType> HAS_ID = REGISTRY.register("has_id",
() -> ConstantLootConditionSerializer.type(HasComputerIdLootCondition.INSTANCE));
() -> new LootItemConditionType(Codec.unit(HasComputerIdLootCondition.INSTANCE)));
}
public static class RecipeSerializers {
@@ -466,8 +467,8 @@ public final class ModRegistry {
* Register any objects which must be done on the main thread.
*/
public static void registerMainThread() {
CauldronInteraction.WATER.put(Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
CauldronInteraction.WATER.put(Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
CauldronInteraction.WATER.map().put(Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
CauldronInteraction.WATER.map().put(Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
}
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {

View File

@@ -54,7 +54,12 @@ import static net.minecraft.commands.Commands.literal;
public final class CommandComputerCraft {
public static final UUID SYSTEM_UUID = new UUID(0, 0);
public static final String OPEN_COMPUTER = "computercraft open-computer ";
/**
* The client-side command to open the folder. Ideally this would live under the main {@code computercraft}
* namespace, but unfortunately that overrides commands, rather than merging them.
*/
public static final String CLIENT_OPEN_FOLDER = "computercraft-computer-folder";
private CommandComputerCraft() {
}
@@ -389,7 +394,7 @@ public final class CommandComputerCraft {
return link(
text("\u270E"),
"/" + OPEN_COMPUTER + id,
"/" + CLIENT_OPEN_FOLDER + " " + id,
Component.translatable("commands.computercraft.dump.open_path")
);
}

View File

@@ -8,8 +8,9 @@ import com.google.gson.JsonObject;
import com.mojang.brigadier.Message;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import dan200.computercraft.shared.platform.RegistryWrappers;
import dan200.computercraft.impl.RegistryHelper;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
@@ -24,7 +25,7 @@ public class ArgumentUtils {
public static <A extends ArgumentType<?>> JsonObject serializeToJson(ArgumentTypeInfo.Template<A> template) {
var object = new JsonObject();
object.addProperty("type", "argument");
object.addProperty("parser", RegistryWrappers.COMMAND_ARGUMENT_TYPES.getKey(template.type()).toString());
object.addProperty("parser", RegistryHelper.getKeyOrThrow(BuiltInRegistries.COMMAND_ARGUMENT_TYPE, template.type()).toString());
var properties = new JsonObject();
serializeToJson(properties, template.type(), template);
@@ -44,12 +45,12 @@ public class ArgumentUtils {
@SuppressWarnings("unchecked")
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void serializeToNetwork(FriendlyByteBuf buffer, ArgumentTypeInfo<A, T> type, ArgumentTypeInfo.Template<A> template) {
buffer.writeId(RegistryWrappers.COMMAND_ARGUMENT_TYPES, type);
buffer.writeId(BuiltInRegistries.COMMAND_ARGUMENT_TYPE, type);
type.serializeToNetwork((T) template, buffer);
}
public static ArgumentTypeInfo.Template<?> deserialize(FriendlyByteBuf buffer) {
var type = buffer.readById(RegistryWrappers.COMMAND_ARGUMENT_TYPES);
var type = buffer.readById(BuiltInRegistries.COMMAND_ARGUMENT_TYPE);
Objects.requireNonNull(type, "Unknown argument type");
return type.deserializeFromNetwork(buffer);
}

View File

@@ -7,7 +7,7 @@ package dan200.computercraft.shared.command.text;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.command.CommandUtils;
import dan200.computercraft.shared.network.client.ChatTableClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.network.server.ServerNetworking;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
@@ -105,7 +105,7 @@ public class TableBuilder {
if (CommandUtils.isPlayer(source)) {
trim(18);
var player = (ServerPlayer) Nullability.assertNonNull(source.getEntity());
PlatformHelper.get().sendToPlayer(new ChatTableClientMessage(this), player);
ServerNetworking.sendToPlayer(new ChatTableClientMessage(this), player);
} else {
trim(100);
new ServerTableFormatter(source).display(this);

View File

@@ -7,7 +7,6 @@ package dan200.computercraft.shared.common;
import dan200.computercraft.shared.ModRegistry;
import net.minecraft.core.NonNullList;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
@@ -20,8 +19,8 @@ import net.minecraft.world.level.Level;
* Craft a wet sponge with a {@linkplain IColouredItem dyable item} to remove its dye.
*/
public final class ClearColourRecipe extends CustomRecipe {
public ClearColourRecipe(ResourceLocation id, CraftingBookCategory category) {
super(id, category);
public ClearColourRecipe(CraftingBookCategory category) {
super(category);
}
@Override

View File

@@ -8,7 +8,6 @@ import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.util.ColourTracker;
import dan200.computercraft.shared.util.ColourUtils;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingBookCategory;
@@ -17,8 +16,8 @@ import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.Level;
public final class ColourableRecipe extends CustomRecipe {
public ColourableRecipe(ResourceLocation id, CraftingBookCategory category) {
super(id, category);
public ColourableRecipe(CraftingBookCategory category) {
super(category);
}
@Override

View File

@@ -56,14 +56,22 @@ public class CommandAPI implements ILuaAPI {
var receiver = computer.getReceiver();
try {
receiver.clearOutput();
var result = commandManager.performPrefixedCommand(computer.getSource(), command);
return new Object[]{ result > 0, receiver.copyOutput(), result };
var state = new CommandState();
var source = computer.getSource().withCallback((success, x) -> {
if (success) state.successes++;
});
commandManager.performPrefixedCommand(source, command);
return new Object[]{ state.successes > 0, receiver.copyOutput(), state.successes };
} catch (Throwable t) {
LOG.error(Logging.JAVA_ERROR, "Error running command.", t);
return new Object[]{ false, createOutput("Java Exception Thrown: " + t) };
}
}
private static final class CommandState {
int successes;
}
private static Map<?, ?> getBlockInfo(Level world, BlockPos pos) {
// Get the details of the block
var block = new BlockReference(world, pos);

View File

@@ -99,7 +99,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
@Override
@Deprecated
public ItemStack getCloneItemStack(BlockGetter world, BlockPos pos, BlockState state) {
public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState state) {
var tile = world.getBlockEntity(pos);
if (tile instanceof AbstractComputerBlockEntity computer) {
var result = getItem(computer);
@@ -117,9 +117,9 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
}
@Override
public void playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
super.playerWillDestroy(world, pos, state, player);
if (!(world instanceof ServerLevel serverWorld)) return;
public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
var result = super.playerWillDestroy(world, pos, state, player);
if (!(world instanceof ServerLevel serverWorld)) return result;
// We drop the item here instead of doing it in the harvest method, as we should
// drop computers for creative players too.
@@ -138,6 +138,8 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
state.spawnAfterBreak(serverWorld, pos, player.getMainHandItem(), true);
}
return result;
}
@Override

View File

@@ -25,7 +25,6 @@ import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.*;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
@@ -51,7 +50,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
private boolean fresh = false;
private int invalidSides = 0;
private final ComponentAccess<IPeripheral> peripherals = PlatformHelper.get().createPeripheralAccess(d -> invalidSides |= 1 << d.ordinal());
private final ComponentAccess<IPeripheral> peripherals = PlatformHelper.get().createPeripheralAccess(this, d -> invalidSides |= 1 << d.ordinal());
private LockCode lockCode = LockCode.NO_LOCK;
@@ -218,7 +217,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
var localDir = remapToLocalSide(dir);
if (isPeripheralBlockedOnSide(localDir)) return;
var peripheral = peripherals.get((ServerLevel) getLevel(), getBlockPos(), dir);
var peripheral = peripherals.get(dir);
computer.setPeripheral(localDir, peripheral);
}

View File

@@ -4,7 +4,10 @@
package dan200.computercraft.shared.computer.blocks;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockCodecs;
import net.minecraft.world.level.block.GameMasterBlock;
import net.minecraft.world.level.block.entity.BlockEntityType;
@@ -16,7 +19,17 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
* @see dan200.computercraft.shared.computer.items.CommandComputerItem
*/
public class CommandComputerBlock<T extends CommandComputerBlockEntity> extends ComputerBlock<T> implements GameMasterBlock {
private static final MapCodec<CommandComputerBlock<?>> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
BlockCodecs.propertiesCodec(),
BlockCodecs.blockEntityCodec(x -> x.type)
).apply(instance, CommandComputerBlock::new));
public CommandComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
super(settings, type);
}
@Override
protected MapCodec<? extends CommandComputerBlock<?>> codec() {
return CODEC;
}
}

View File

@@ -20,28 +20,24 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
public class CommandComputerBlockEntity extends ComputerBlockEntity {
public class CommandReceiver implements CommandSource {
private final Map<Integer, String> output = new HashMap<>();
private final List<String> output = new ArrayList<>();
public void clearOutput() {
output.clear();
}
public Map<Integer, String> getOutput() {
return output;
}
public Map<Integer, String> copyOutput() {
return new HashMap<>(output);
public List<String> copyOutput() {
return new ArrayList<>(output);
}
@Override
public void sendSystemMessage(Component textComponent) {
output.put(output.size() + 1, textComponent.getString());
output.add(textComponent.getString());
}
@Override

View File

@@ -4,9 +4,12 @@
package dan200.computercraft.shared.computer.blocks;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.items.ComputerItem;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockCodecs;
import net.minecraft.core.Direction;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
@@ -21,6 +24,11 @@ import net.minecraft.world.level.block.state.properties.EnumProperty;
import javax.annotation.Nullable;
public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComputerBlock<T> {
private static final MapCodec<ComputerBlock<?>> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
BlockCodecs.propertiesCodec(),
BlockCodecs.blockEntityCodec(x -> x.type)
).apply(instance, ComputerBlock::new));
public static final EnumProperty<ComputerState> STATE = EnumProperty.create("state", ComputerState.class);
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
@@ -37,6 +45,11 @@ public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComput
builder.add(FACING, STATE);
}
@Override
protected MapCodec<? extends ComputerBlock<?>> codec() {
return CODEC;
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext placement) {

View File

@@ -20,7 +20,7 @@ import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.client.ComputerTerminalClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.network.server.ServerNetworking;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -142,7 +142,7 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
for (var player : server.getPlayerList().getPlayers()) {
if (player.containerMenu instanceof ComputerMenu && ((ComputerMenu) player.containerMenu).getComputer() == this) {
PlatformHelper.get().sendToPlayer(createPacket.apply(player.containerMenu), player);
ServerNetworking.sendToPlayer(createPacket.apply(player.containerMenu), player);
}
}
}

View File

@@ -11,7 +11,7 @@ import dan200.computercraft.shared.computer.upload.FileSlice;
import dan200.computercraft.shared.computer.upload.FileUpload;
import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.network.client.UploadResultMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.network.server.ServerNetworking;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.network.chat.Component;
@@ -138,7 +138,7 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
return;
}
PlatformHelper.get().sendToPlayer(finishUpload(uploader), uploader);
ServerNetworking.sendToPlayer(finishUpload(uploader), uploader);
}
private UploadResultMessage finishUpload(ServerPlayer player) {
@@ -159,7 +159,7 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
toUpload.stream().map(x -> new TransferredFile(x.getName(), new ByteBufferChannel(x.getBytes()))).toList(),
() -> {
if (player.isAlive() && player.containerMenu == owner) {
PlatformHelper.get().sendToPlayer(UploadResultMessage.consumed(owner), player);
ServerNetworking.sendToPlayer(UploadResultMessage.consumed(owner), player);
}
}),
});

View File

@@ -8,7 +8,6 @@ import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.recipe.CustomShapedRecipe;
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
@@ -17,8 +16,8 @@ import net.minecraft.world.level.Level;
* A recipe which converts a computer from one form into another.
*/
public abstract class ComputerConvertRecipe extends CustomShapedRecipe {
public ComputerConvertRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe) {
super(identifier, recipe);
public ComputerConvertRecipe(ShapedRecipeSpec recipe) {
super(recipe);
}
protected abstract ItemStack convert(IComputerItem item, ItemStack stack);

View File

@@ -8,7 +8,6 @@ import com.mojang.serialization.DataResult;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
@@ -22,17 +21,17 @@ import net.minecraft.world.item.crafting.RecipeSerializer;
public final class ComputerUpgradeRecipe extends ComputerConvertRecipe {
private final Item result;
private ComputerUpgradeRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe) {
super(identifier, recipe);
public ComputerUpgradeRecipe(ShapedRecipeSpec recipe) {
super(recipe);
this.result = recipe.result().getItem();
}
public static DataResult<ComputerUpgradeRecipe> of(ResourceLocation id, ShapedRecipeSpec recipe) {
public static DataResult<ComputerUpgradeRecipe> of(ShapedRecipeSpec recipe) {
if (!(recipe.result().getItem() instanceof IComputerItem)) {
return DataResult.error(() -> recipe.result().getItem() + " is not a computer item");
}
return DataResult.success(new ComputerUpgradeRecipe(id, recipe));
return DataResult.success(new ComputerUpgradeRecipe(recipe));
}
@Override

View File

@@ -224,7 +224,7 @@ public final class ConfigSpec {
.defineInRange("max_requests", CoreConfig.httpMaxRequests, 0, Integer.MAX_VALUE);
httpMaxWebsockets = builder
.comment("The number of websockets a computer can have open at one time. Set to 0 for unlimited.")
.comment("The number of websockets a computer can have open at one time.")
.defineInRange("max_websockets", CoreConfig.httpMaxWebsockets, 1, Integer.MAX_VALUE);
builder

View File

@@ -1,33 +0,0 @@
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.data;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import net.minecraft.world.level.storage.loot.Serializer;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
public final class ConstantLootConditionSerializer<T extends LootItemCondition> implements Serializer<T> {
private final T instance;
public ConstantLootConditionSerializer(T instance) {
this.instance = instance;
}
public static <T extends LootItemCondition> LootItemConditionType type(T condition) {
return new LootItemConditionType(new ConstantLootConditionSerializer<>(condition));
}
@Override
public void serialize(JsonObject json, T object, JsonSerializationContext context) {
}
@Override
public T deserialize(JsonObject json, JsonDeserializationContext context) {
return instance;
}
}

View File

@@ -5,7 +5,7 @@
package dan200.computercraft.shared.details;
import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.shared.platform.RegistryWrappers;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.state.properties.Property;
import java.util.HashMap;
@@ -15,7 +15,7 @@ public class BlockDetails {
public static void fillBasic(Map<? super String, Object> data, BlockReference block) {
var state = block.state();
data.put("name", DetailHelpers.getId(RegistryWrappers.BLOCKS, state.getBlock()));
data.put("name", DetailHelpers.getId(BuiltInRegistries.BLOCK, state.getBlock()));
Map<Object, Object> stateTable = new HashMap<>();
for (Map.Entry<Property<?>, ? extends Comparable<?>> entry : state.getValues().entrySet()) {

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