1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-23 09:57:39 +00:00

Compare commits

...

60 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
Jonathan Coates
8be6b1b772 Bump CC:T to 1.109.3 2024-01-03 20:00:52 +00:00
Jonathan Coates
104d5e70de Update to Cobalt 0.8.2
- Fix error when using "goto" as the first statement in an if block.
 - Fix incorrect resizing of table's hash part when adding and removing
   keys.
2024-01-03 18:44:37 +00:00
Jonathan Coates
e3bda2f763 Add command computers to the operator blocks tab
Fixes #1666
2024-01-03 18:42:31 +00:00
Jonathan Coates
234f69e8e5 Add a MessageType for network messages
Everything old is new again!

CC's network message implementation has gone through several iterations:

 - Originally network messages were implemented with a single class,
   which held an packet id/type and and opaque blobs of data (as
   string/int/byte/NBT arrays), and a big switch statement to decode and
   process this data.

 - In 42d3901ee3, we split the messages
   into different classes all inheriting from NetworkMessage - this bit
   we've stuck with ever since.

   Each packet had a `getId(): int` method, which returned the
   discriminator for this packet.

 - However, getId() was only used when registering the packet, not when
   sending, and so in ce0685c31f we
   removed it, just passing in a constant integer at registration
   instead.

 - In 53abe5e56e, we made some relatively
   minor changes to make the code more multi-loader/split-source
   friendly. However, this meant when we finally came to add Fabric
   support (8152f19b6e), we had to
   re-implement a lot of Forge's network code.

In 1.20.4, Forge moves to a system much closer to Fabric's (and indeed,
Minecraft's own CustomPacketPayload), and so it makes sense to adapt to
that now. As such, we:

 - Add a new MessageType interface. This is implemented by the
   loader-specific modules, and holds whatever information is needed to
   register the packet (e.g. discriminator, reader function).

 - Each NetworkMessage now has a type(): MessageType<?> function. This
   is used by the Fabric networking code (and for NeoForge's on 1.20.4)
   instead of a class lookup.

 - NetworkMessages now creates/stores these MessageType<T>s (much like
   we'd do for registries), and provides getters for the
   clientbound/serverbound messages. Mod initialisers then call these
   getters to register packets.

 - For Forge, this is relatively unchanged. For Fabric, we now
   `FabricPacket`s.
2024-01-03 10:23:41 +00:00
Jonathan Coates
ed3a17f9b9 Fix trailing-comma errors on method calls
We were only matching `f(a, ` patterns, and not `x:f(a, `. We now just
match against any usages of call_args - hadn't quite realised we could
do that!
2023-12-28 17:07:39 +00:00
Jonathan Coates
0349c2b1f9 Allow local domains in the standalone emulator
- Clean up option parsing a bit, so it uses the Option, rather than its
   corresponding character code.
 - Add a new -L/--allow-local-domains flag to remove the $private rule
   from the HTTP rules.
2023-12-20 17:43:08 +00:00
Jonathan Coates
03f9e6bd6d Prevent sending too many websocket messages at once 2023-12-20 17:30:57 +00:00
Jonathan Coates
9d8c933a14 Remove several usages of ComputerFamily
While ComputerFamily is still useful, there's definitely some places
where it adds an extra layer of indirection. This commit attempts to
clean up some places where we no longer need it.

 - Remove ComputerFamily from AbstractComputerBlock. The only place this
   was needed is in TurtleBlock, and that can be replaced with normal
   Minecraft explosion resistence!

 - Pass in the fuel limit to the turtle block entity, rather than
   deriving it from current family.

 - The turtle BERs now derive their model from the turtle's item, rather
   than the turtle's family.

 - When creating upgrade/overlay recipes, use the item's name, rather
   than {pocket,turtle}_family. This means we can drop getFamily() from
   IComputerItem (it is still needed on to handle the UI).

 - We replace IComputerItem.withFamily with a method to change to a
   different item of the same type. ComputerUpgradeRecipe no longer
   takes a family, and instead just uses the result's item.

 - Computer blocks now use the normal Block.asItem() to find their
   corresponding item, rather than looking it up via family.

The above means we can remove all the family-based XyzItem.create(...)
methods, which have always felt a little ugly.

We still need ComputerFamily for a couple of things:
 - Permission checks for command computers.
 - Checks for mouse/colour support in ServerComputer.
 - UI textures.
2023-12-20 14:17:38 +00:00
Jonathan Coates
78bb3da58c Improve our version tooling
- Add a check to ensure declared dependencies in the :core project, and
   those inherited from Minecraft are the same.
 - Compute the next Cobalt version, rather than specifying it manually.
 - Add the gradle versions plugin (and version catalog update), and
   update some versions.
2023-12-19 18:12:21 +00:00
Jonathan Coates
39a5e40c92 Bump CC:T to 1.109.2
Take two!
2023-12-16 22:46:30 +00:00
Jonathan Coates
763ba51919 Update Cobalt to 0.8.1 2023-12-16 22:39:48 +00:00
Jonathan Coates
cf6ec8c28f Add a slightly cleaner system for excluding deps
Previously we prevented our published full jar depending on any of the
other projects by excluding the whole cc.tweaked jar. However, as Cobalt
also now lives in that group, this meant we were missing the Cobalt
dependency.

Rather than specifying a wildcard, we now exclude the dependencies when
adding them to the project.
2023-12-16 22:35:15 +00:00
Jonathan Coates
95d3b646b2 Bump CC:T to 1.109.1 2023-12-16 19:09:39 +00:00
Jonathan Coates
488f66eead Fix mouse_drag not firing for right/middle buttons
This is a bit of an odd combination of a few bugs:
 - When the terminal component is blurred, we fire a mouse_up event for
   the last-held button. However, we had an off-by-1 error here, so this
   only triggered for the right/middle buttons.

 - This was obsucuring the second bug, which is when we clicked within
   the terminal, this caused the terminal to be blurred (thus releasing
   the mouse) and then focused again.

   We fix this by only setting the focus if there's actually a change.

Fixes #1655
2023-12-10 12:01:34 +00:00
Jonathan Coates
1f7d245876 Specify charset when printing error messages 2023-12-08 09:52:17 +00:00
Jonathan Coates
af12b3a0ea Fix goto/:: tokens erroring in error reporting 2023-12-07 19:47:39 +00:00
Jonathan Coates
eb3e8ba677 Fix deadlock when adding/removing observers
When adding/removing observers, we locked on the observer, then
acquired the global lock. When a metric is observed, then we acquire the
global lock and then the observer lock.

If these happen at the same time, we can easily end up with a deadlock.
We simply avoid holding the observer lock for the entire add/remove
process (instead only locking when actually needed).

Closes #1639
2023-12-01 12:33:03 +00:00
Jonathan Coates
2043939531 Add compostors to the list of usable blocks
Fixes #1638
2023-11-22 18:24:59 +00:00
Jonathan Coates
84a799d27a Add abstract classes for our generic peripherals
This commit adds abstract classes to describe the interface for our
mod-loader-specific generic peripherals (inventories, fluid storage,
item storage).

This offers several advantages:
 - Javadoc to illuaminate conversion no longer needs the Forge project
   (just core and common).

 - Ensures we have a consistent interface between Forge and Fabric.

Note, this does /not/ implement fluid or energy storage for Fabric. We
probably could do fluid without issue, but not something worth doing
right now.
2023-11-22 18:20:15 +00:00
Jonathan Coates
fe826f5c9c Allow generic sources to have instance methods
Rather than assuming static methods are generic, and instance methods
are direct, the Generator now has separate entrypoints for handling
instance and generic methods.

As a result of this change, we've also relaxed some of the validation
code. As a result, we now allow calling private/protected methods
which are annotated with @LuaFunction.
2023-11-22 10:06:11 +00:00
Jonathan Coates
f8b7422294 Fix several issues with the web emulator
- Bump TeaVM version to fix issues with locales and our "export"
   generation.
 - Fix TComputerThread not requeuing correctly.
2023-11-15 13:12:31 +00:00
Jonathan Coates
b343c01216 Bump CC:T to 1.109.0 2023-11-15 09:39:52 +00:00
Jonathan Coates
76968f2f28 Track allocations while executing computers
This adds a new "java_allocation" metric, which tracks the number of
bytes allocated while executing the computer (as measured by Java). This
is not an 100% reliable number, but hopefully gives some insight into
what computers are doing.
2023-11-09 18:36:35 +00:00
Jonathan Coates
1d365f5a0b Add option to allow repetition in JSON serialiser
Closes #1588
2023-11-08 21:29:43 +00:00
Jonathan Coates
7b240cbf7e Merge pull request #1615 from cc-tweaked/feature/much-breakage-very-wow
Remove text mode, update Cobalt
2023-11-08 20:05:49 +00:00
Jonathan Coates
d272a327c7 Update CraftOS version to 1.9 2023-11-08 19:40:14 +00:00
Jonathan Coates
0c0556a5bc Always use raw bytes in file handles
Historically CC has supported two modes when working with file handles
(and HTTP requests):

 - Text mode, which reads/write using UTF-8.
 - Binary mode, which reads/writes the raw bytes.

However, this can be confusing at times. CC/Lua doesn't actually support
unicode, so any characters beyond the 0.255 range were replaced with
'?'. This meant that most of the time you were better off just using
binary mode.

This commit unifies text and binary mode - we now /always/ read the raw
bytes of the file, rather than converting to/from UTF-8. Binary mode now
only specifies whether handle.read() returns a number (and .write(123)
writes a byte rather than coercing to a string).

 - Refactor the entire handle hierarchy. We now have an AbstractMount
   base class, which has the concrete implementation of all methods. The
   public-facing classes then re-export these methods by annotating
   them with @LuaFunction.

   These implementations are based on the
   Binary{Readable,Writable}Handle classes. The Encoded{..}Handle
   versions are now entirely removed.

 - As we no longer need to use BufferedReader/BufferedWriter, we can
   remove quite a lot of logic in Filesystem to handle wrapping
   closeable objects.

 - Add a new WritableMount.openFile method, which generalises
   openForWrite/openForAppend to accept OpenOptions. This allows us to
   support update mode (r+, w+) in fs.open.

 - fs.open now uses the new handle types, and supports update (r+, w+)
   mode.

 - http.request now uses the new readable handle type. We no longer
   encode the request body to UTF-8, nor decode the response from UTF-8.

 - Websockets now return text frame's contents directly, rather than
   converting it from UTF-8. Sending text frames now attempts to treat
   the passed string as UTF-8, rather than treating it as latin1.
2023-11-08 19:40:14 +00:00
Jonathan Coates
87345c6b2e Add pasting support to the standalone emulator
- Move paste normalisation code to StringUtil, so it can be shared by
   emulators.
 - Add paste support to the emulator.
2023-11-08 19:40:14 +00:00
Jonathan Coates
784e623776 Update Cobalt to 0.8.0
- Update Cobalt to 0.8.0, switching our Lua version to 5.2(ish).

 - Remove our `load` wrapper, as we no longer need to inject _ENV into
   the enviroment table.

 - Update the parser to handle labels and goto. This doesn't check that
   gotos are well formed, but at least means the parser doesn't fall
   over on them.

 - Update our docs to reflect the changes to Cobalt.
2023-11-08 18:42:17 +00:00
429 changed files with 7720 additions and 6369 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

@@ -42,7 +42,6 @@ repositories {
url "https://squiddev.cc/maven/"
content {
includeGroup("cc.tweaked")
includeModule("org.squiddev", "Cobalt")
}
}
}
@@ -52,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")
@@ -76,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

@@ -13,6 +13,8 @@ plugins {
publishing
alias(libs.plugins.taskTree)
alias(libs.plugins.githubRelease)
alias(libs.plugins.gradleVersions)
alias(libs.plugins.versionCatalogUpdate)
id("org.jetbrains.gradle.plugin.idea-ext")
id("cc-tweaked")
}
@@ -102,3 +104,9 @@ idea.project.settings.compiler.javac {
}
.toMap()
}
versionCatalogUpdate {
sortByKey.set(false)
pin { versions.addAll("fastutil", "guava", "netty", "slf4j") }
keep { keepUnusedLibraries.set(true) }
}

View File

@@ -5,6 +5,8 @@
plugins {
`java-gradle-plugin`
`kotlin-dsl`
alias(libs.plugins.gradleVersions)
alias(libs.plugins.versionCatalogUpdate)
}
// Duplicated in settings.gradle.kts
@@ -12,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")
}
}
@@ -40,6 +31,13 @@ repositories {
includeGroup("net.fabricmc")
}
}
maven("https://squiddev.cc/maven") {
name = "SquidDev"
content {
includeGroup("cc.tweaked.vanilla-extract")
}
}
}
dependencies {
@@ -49,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 {
@@ -75,3 +71,9 @@ gradlePlugin {
}
}
}
versionCatalogUpdate {
sortByKey.set(false)
keep { keepUnusedLibraries.set(true) }
catalogFile.set(file("../gradle/libs.versions.toml"))
}

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,24 +40,12 @@ 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")
includeModule("org.squiddev", "Cobalt")
// Things we mirror
includeGroup("commoble.morered")
includeGroup("dev.architectury")
@@ -77,6 +65,12 @@ dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
checkstyle(libs.findLibrary("checkstyle").get())
constraints {
checkstyle("org.codehaus.plexus:plexus-container-default:2.1.1") {
because("2.1.0 depends on deprecated Google collections module")
}
}
errorprone(libs.findLibrary("errorProne-core").get())
errorprone(libs.findLibrary("nullAway").get())
}
@@ -202,6 +196,7 @@ spotless {
val ktlintConfig = mapOf(
"ktlint_standard_no-wildcard-imports" to "disabled",
"ktlint_standard_class-naming" to "disabled",
"ktlint_standard_function-naming" to "disabled",
"ij_kotlin_allow_trailing_comma" to "true",
"ij_kotlin_allow_trailing_comma_on_call_site" to "true",
)

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

@@ -10,9 +10,11 @@ import org.gradle.api.GradleException
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.TestSuiteType
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.reporting.ReportingExtension
@@ -73,11 +75,17 @@ abstract class CCTweakedExtension(
*/
val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java)
/**
* Dependencies excluded from published artifacts.
*/
private val excludedDeps: ListProperty<Dependency> = project.objects.listProperty(Dependency::class.java)
/** All source sets referenced by this project. */
val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } }
init {
sourceDirectories.finalizeValueOnRead()
excludedDeps.finalizeValueOnRead()
project.afterEvaluate { sourceDirectories.disallowChanges() }
}
@@ -246,6 +254,20 @@ abstract class CCTweakedExtension(
).resolve().single()
}
/**
* Exclude a dependency from being published in Maven.
*/
fun exclude(dep: Dependency) {
excludedDeps.add(dep)
}
/**
* Configure a [MavenDependencySpec].
*/
fun configureExcludes(spec: MavenDependencySpec) {
for (dep in excludedDeps.get()) spec.exclude(dep)
}
companion object {
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
private val IGNORED_USERS = setOf(

View File

@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
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
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
fun run() {
var ok = true
for (configuration in configuration.get()) {
configuration.incoming.resolutionResult.allDependencies {
if (!check(this@allDependencies)) ok = false
}
}
if (!ok) {
throw GradleException("Mismatched versions in Minecraft dependencies. gradle/libs.versions.toml may need updating.")
}
}
private fun check(dependency: DependencyResult): Boolean {
if (dependency !is ResolvedDependencyResult) {
logger.warn("Found unexpected dependency result {}", dependency)
return false
}
// Skip dependencies on non-modules.
val requested = dependency.requested
if (requested !is ModuleComponentSelector) return true
// If this dependency is specified within some project (so is non-transitive), or is pulled in via Minecraft,
// then check for consistency.
// It would be nice to be smarter about transitive dependencies, but avoiding false positives is hard.
val from = dependency.from.id
if (
from is ProjectComponentIdentifier ||
from is ModuleComponentIdentifier && (from.group == "net.minecraft" || from.group == "io.netty")
) {
// If the version is different between the requested and selected version, report an error.
val selected = dependency.selected.moduleVersion!!.version
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
}
return true
}
return true
}
}

View File

@@ -6,7 +6,6 @@ package cc.tweaked.gradle
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.file.FileSystemLocationProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.JavaExec
@@ -129,3 +128,30 @@ fun <T> Property<T>.setProvider(provider: Provider<out T>) = set(provider)
/** Short-cut method to get the absolute path of a [FileSystemLocation] provider. */
fun Provider<out FileSystemLocation>.getAbsolutePath(): String = get().asFile.absolutePath
/**
* Get the version immediately after the provided version.
*
* For example, given "1.2.3", this will return "1.2.4".
*/
fun getNextVersion(version: String): String {
// Split a version like x.y.z-SNAPSHOT into x.y.z and -SNAPSHOT
val dashIndex = version.indexOf('-')
val mainVersion = if (dashIndex < 0) version else version.substring(0, dashIndex)
// Find the last component in x.y.z and increment it.
val lastIndex = mainVersion.lastIndexOf('.')
if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"")
val lastVersion = try {
version.substring(lastIndex + 1).toInt()
} catch (e: NumberFormatException) {
throw IllegalArgumentException("Cannot parse version format \"$version\"", e)
}
// Then append all components together.
val out = StringBuilder()
out.append(version, 0, lastIndex + 1)
out.append(lastVersion + 1)
if (dashIndex >= 0) out.append(version, dashIndex, version.length)
return out.toString()
}

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

@@ -6,6 +6,8 @@ package cc.tweaked.gradle
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.plugins.BasePluginExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.specs.Spec
@@ -26,8 +28,13 @@ class MavenDependencySpec {
fun exclude(dep: Dependency) {
exclude {
// We have to cheat a little for project dependencies, as the project name doesn't match the artifact group.
val name = when (dep) {
is ProjectDependency -> dep.dependencyProject.extensions.getByType(BasePluginExtension::class.java).archivesName.get()
else -> dep.name
}
(dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
(dep.name.isNullOrEmpty() || dep.name == it.artifactId) &&
(name.isNullOrEmpty() || name == it.artifactId) &&
(dep.version.isNullOrEmpty() || dep.version == it.version)
}
}

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,88 +82,39 @@ 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))
}
}
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))
// Register a task to check there are no conflicts with the core project.
val checkDependencyConsistency =
project.tasks.register("checkDependencyConsistency", DependencyCheck::class.java) {
// We need to check both the main and client classpath *configurations*, as the actual configuration
configuration.add(configurations.named(main.runtimeClasspathConfigurationName))
configuration.add(configurations.named(client.runtimeClasspathConfigurationName))
}
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) } }
}
}
}
project.tasks.named("check") { dependsOn(checkDependencyConsistency) }
}
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

@@ -12,7 +12,7 @@ SPDX-License-Identifier: MPL-2.0
The [`file_transfer`] event is queued when a user drags-and-drops a file on an open computer.
This event contains a single argument of type [`TransferredFiles`], which can be used to [get the files to be
transferred][`TransferredFiles.getFiles`]. Each file returned is a [binary file handle][`fs.BinaryReadHandle`] with an
transferred][`TransferredFiles.getFiles`]. Each file returned is a [binary file handle][`fs.ReadHandle`] with an
additional [getName][`TransferredFile.getName`] method.
## Return values

View File

@@ -134,7 +134,7 @@ accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, whic
As mentioned above, [`speaker.playAudio`] accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
[`io.lines`], which provides a nice way to loop over chunks of a file. You can of course just use [`fs.open`] and
[`fs.BinaryReadHandle.read`] if you prefer.
[`fs.ReadHandle.read`] if you prefer.
## Processing audio
As mentioned near the beginning of this guide, PCM audio is pretty easy to work with as it's just a list of amplitudes.

View File

@@ -21,6 +21,20 @@ 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.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.
- Environments are no longer baked into the runtime, and instead use the `_ENV` local or upvalue. `getfenv`/`setfenv`
now only work on Lua functions with an `_ENV` upvalue. `getfenv` will return the global environment when called
with other functions, and `setfenv` will have no effect.
- `load`/`loadstring` defaults to using the global environment (`_G`) rather than the current coroutine's
environment.
- Support for dumping functions (`string.dump`) and loading binary chunks has been removed.
- `math.random` now uses Lua 5.4's random number generator.
- File handles, HTTP requests and websockets now always use the original bytes rather than encoding/decoding to UTF-8.
## Minecraft 1.13 {#mc-1.13}
- The "key code" for [`key`] and [`key_up`] events has changed, due to Minecraft updating to LWJGL 3. Make sure you're
using the constants provided by the [`keys`] API, rather than hard-coding numerical values.
@@ -62,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,17 +9,19 @@ SPDX-License-Identifier: MPL-2.0
-->
# Lua 5.2/5.3 features in CC: Tweaked
CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However, Cobalt and CC:T implement additional features from Lua 5.2 and 5.3 (as well as some deprecated 5.0 features) that are not available in base 5.1. This page lists all of the compatibility for these newer versions.
CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.2. However, Cobalt and CC:T implement additional
features from Lua 5.2 and 5.3 (as well as some deprecated 5.0 and 5.1 features). This page lists all of the
compatibility for these newer versions.
## Lua 5.2
| Feature | Supported? | Notes |
|---------------------------------------------------------------|------------|-------------------------------------------------------------------|
| `goto`/labels | | |
| `_ENV` | 🔶 | The `_ENV` global points to `getfenv()`, but it cannot be set. |
| `goto`/labels | | |
| `_ENV` | | |
| `\z` escape | ✔ | |
| `\xNN` escape | ✔ | |
| Hex literal fractional/exponent parts | ✔ | |
| Empty statements | | |
| Empty statements | | |
| `__len` metamethod | ✔ | |
| `__ipairs` metamethod | ❌ | Deprecated in Lua 5.3. `ipairs` uses `__len`/`__index` instead. |
| `__pairs` metamethod | ✔ | |
@@ -27,12 +29,12 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
| `collectgarbage` isrunning, generational, incremental options | ❌ | `collectgarbage` does not exist in CC:T. |
| New `load` syntax | ✔ | |
| `loadfile` mode parameter | ✔ | Supports both 5.1 and 5.2+ syntax. |
| Removed `loadstring` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Removed `getfenv`, `setfenv` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Removed `loadstring` | | |
| Removed `getfenv`, `setfenv` | 🔶 | Only supports closures with an `_ENV` upvalue. |
| `rawlen` function | ✔ | |
| Negative index to `select` | ✔ | |
| Removed `unpack` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Arguments to `xpcall` | ✔ | |
| Removed `unpack` | | |
| Arguments to `xpcall` | ✔ | |
| Second return value from `coroutine.running` | ✔ | |
| Removed `module` | ✔ | |
| `package.loaders` -> `package.searchers` | ❌ | |
@@ -40,14 +42,14 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
| `package.config` | ✔ | |
| `package.searchpath` | ✔ | |
| Removed `package.seeall` | ✔ | |
| `string.dump` on functions with upvalues (blanks them out) | | |
| `string.rep` separator | ✔ | |
| `string.dump` on functions with upvalues (blanks them out) | | `string.dump` is not supported |
| `string.rep` separator | ✔ | |
| `%g` match group | ❌ | |
| Removal of `%z` match group | ❌ | |
| Removed `table.maxn` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Removed `table.maxn` | | |
| `table.pack`/`table.unpack` | ✔ | |
| `math.log` base argument | ✔ | |
| Removed `math.log10` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Removed `math.log10` | | |
| `*L` mode to `file:read` | ✔ | |
| `os.execute` exit type + return value | ❌ | `os.execute` does not exist in CC:T. |
| `os.exit` close argument | ❌ | `os.exit` does not exist in CC:T. |
@@ -61,7 +63,7 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
| Tail call hooks | ❌ | |
| `=` prefix for chunks | ✔ | |
| Yield across C boundary | ✔ | |
| Removal of ambiguity error | | |
| Removal of ambiguity error | | |
| Identifiers may no longer use locale-dependent letters | ✔ | |
| Ephemeron tables | ❌ | |
| Identical functions may be reused | ❌ | Removed in Lua 5.4 |

View File

@@ -95,10 +95,10 @@ function pullEventRaw(filter) end
-- nearest multiple of 0.05.
function sleep(time) end
--- Get the current CraftOS version (for example, `CraftOS 1.8`).
--- Get the current CraftOS version (for example, `CraftOS 1.9`).
--
-- This is defined by `bios.lua`. For the current version of CC:Tweaked, this
-- should return `CraftOS 1.8`.
-- should return `CraftOS 1.9`.
--
-- @treturn string The current CraftOS version.
-- @usage os.version()

View File

@@ -9,8 +9,8 @@ kotlin.stdlib.default.dependency=false
kotlin.jvm.target.validation.mode=error
# Mod properties
isUnstable=false
modVersion=1.108.4
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,70 +7,71 @@
# 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 = "6.0.0"
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"
# Normal dependencies
asm = "9.5"
# Core dependencies (these versions are tied to the version Minecraft uses)
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.32.0"
cobalt = "0.7.3"
cobalt-next = "0.7.4" # Not a real version, used to constrain the version we accept.
commonsCli = "1.3.1"
fastutil = "8.5.9"
guava = "31.1-jre"
jetbrainsAnnotations = "24.0.1"
checkerFramework = "3.42.0"
cobalt = "0.9.0"
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2"
jzlib = "1.1.3"
kotlin = "1.8.10"
kotlin-coroutines = "1.6.4"
netty = "4.1.82.Final"
kotlin = "1.9.21"
kotlin-coroutines = "1.7.3"
nightConfig = "3.6.7"
slf4j = "2.0.1"
# 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"
# Testing
hamcrest = "2.2"
jqwik = "1.7.4"
junit = "5.10.0"
jqwik = "1.8.2"
junit = "5.10.1"
# Build tools
cctJavadoc = "1.8.0"
checkstyle = "10.12.3"
curseForgeGradle = "1.0.14"
errorProne-core = "2.21.1"
cctJavadoc = "1.8.2"
checkstyle = "10.12.6"
curseForgeGradle = "1.1.18"
errorProne-core = "2.23.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.3.7"
forgeGradle = "6.0.8"
githubRelease = "2.4.1"
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.1"
minotaur = "2.+"
mixinGradle = "0.7.+"
lwjgl = "3.3.3"
minotaur = "2.8.7"
neoGradle = "7.0.85"
nullAway = "0.9.9"
spotless = "6.21.0"
spotless = "6.23.3"
taskTree = "2.1.1"
teavm = "0.9.0-SQUID.1"
vanillaGradle = "0.2.1-SNAPSHOT"
vineflower = "1.11.0"
teavm = "0.10.0-SQUID.2"
vanillaExtract = "0.1.1"
versionCatalogUpdate = "0.8.1"
[libraries]
# Normal dependencies
@@ -78,10 +79,10 @@ asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
checkerFramework = { module = "org.checkerframework:checker-qual", version.ref = "checkerFramework" }
cobalt = { module = "org.squiddev:Cobalt", version.ref = "cobalt" }
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" }
@@ -103,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" }
@@ -141,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" }
@@ -156,16 +156,15 @@ 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" }
[bundles]
annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
@@ -174,15 +173,14 @@ 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"]
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]
# Build tools
teavm-api = [ "teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api" ]
teavm-tooling = [ "teavm-tooling", "teavm-metaprogramming-impl", "teavm-jso-impl" ]
teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"]
teavm-tooling = ["teavm-tooling", "teavm-metaprogramming-impl", "teavm-jso-impl"]

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" \

View File

@@ -6,7 +6,7 @@
(sources
/doc/
/projects/forge/build/docs/luaJavadoc/
/projects/common/build/docs/luaJavadoc/
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/
/projects/core/src/test/resources/test-rom
@@ -36,7 +36,7 @@
(library-path
/doc/stub/
/projects/forge/build/docs/luaJavadoc/
/projects/common/build/docs/luaJavadoc/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/command/
@@ -88,7 +88,7 @@
(/doc/stub/
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
/projects/forge/build/docs/luaJavadoc/)
/projects/common/build/docs/luaJavadoc/)
(linters -var:unused-global)
(lint (allow-toplevel-global true)))

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

@@ -2,13 +2,12 @@
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.annotationProcessorEverywhere
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
import cc.tweaked.gradle.*
plugins {
id("cc-tweaked.vanilla")
id("cc-tweaked.gametest")
id("cc-tweaked.illuaminate")
id("cc-tweaked.publishing")
}
@@ -19,6 +18,18 @@ minecraft {
)
}
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"))
@@ -28,7 +39,6 @@ dependencies {
compileOnly(libs.bundles.externalMods.common)
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
compileOnly(libs.mixin)
annotationProcessorEverywhere(libs.autoService)
testFixturesAnnotationProcessor(libs.autoService)
@@ -36,9 +46,59 @@ dependencies {
testImplementation(libs.bundles.test)
testRuntimeOnly(libs.bundles.testRuntime)
testModCompileOnly(libs.mixin)
testModImplementation(testFixtures(project(":core")))
testModImplementation(testFixtures(project(":common")))
testModImplementation(libs.bundles.kotlin)
testFixturesImplementation(testFixtures(project(":core")))
"cctJavadoc"(libs.cctJavadoc)
}
illuaminate {
version.set(libs.versions.illuaminate)
}
val luaJavadoc by tasks.registering(Javadoc::class) {
description = "Generates documentation for Java-side Lua functions."
group = JavaBasePlugin.DOCUMENTATION_GROUP
val sourceSets = listOf(sourceSets.main.get(), project(":core").sourceSets.main.get())
for (sourceSet in sourceSets) {
source(sourceSet.java)
classpath += sourceSet.compileClasspath
}
destinationDir = layout.buildDirectory.dir("docs/luaJavadoc").get().asFile
val options = options as StandardJavadocDocletOptions
options.docletpath = configurations["cctJavadoc"].files.toList()
options.doclet = "cc.tweaked.javadoc.LuaDoclet"
options.addStringOption("project-root", rootProject.file(".").absolutePath)
options.noTimestamp(false)
javadocTool.set(
javaToolchains.javadocToolFor {
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
},
)
}
val lintLua by tasks.registering(IlluaminateExec::class) {
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Lint Lua (and Lua docs) with illuaminate"
// Config files
inputs.file(rootProject.file("illuaminate.sexp")).withPropertyName("illuaminate.sexp")
// Sources
inputs.files(rootProject.fileTree("doc")).withPropertyName("docs")
inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(luaJavadoc)
args = listOf("lint")
workingDir = rootProject.projectDir
doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}

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;
@@ -19,6 +19,7 @@ import dan200.computercraft.shared.network.server.UploadFileMessage;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
@@ -124,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);
}
@@ -144,6 +144,11 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|| super.mouseDragged(x, y, button, deltaX, deltaY);
}
@Override
public void setFocused(@Nullable GuiEventListener listener) {
// Don't clear and re-focus if we're already focused.
if (listener != getFocused()) super.setFocused(listener);
}
@Override
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
@@ -201,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

@@ -8,8 +8,8 @@ import com.mojang.blaze3d.vertex.Tesselator;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.util.StringUtil;
import dan200.computercraft.shared.computer.core.InputHandler;
import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.AbstractWidget;
@@ -112,26 +112,8 @@ public class TerminalWidget extends AbstractWidget {
}
private void paste() {
var clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
// Clip to the first occurrence of \r or \n
var newLineIndex1 = clipboard.indexOf('\r');
var newLineIndex2 = clipboard.indexOf('\n');
if (newLineIndex1 >= 0 && newLineIndex2 >= 0) {
clipboard = clipboard.substring(0, Math.min(newLineIndex1, newLineIndex2));
} else if (newLineIndex1 >= 0) {
clipboard = clipboard.substring(0, newLineIndex1);
} else if (newLineIndex2 >= 0) {
clipboard = clipboard.substring(0, newLineIndex2);
}
// Filter the string
clipboard = SharedConstants.filterText(clipboard);
if (!clipboard.isEmpty()) {
// Clip to 512 characters and queue the event
if (clipboard.length() > 512) clipboard = clipboard.substring(0, 512);
computer.queueEvent("paste", new Object[]{ clipboard });
}
var clipboard = StringUtil.normaliseClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
if (!clipboard.isEmpty()) computer.queueEvent("paste", new Object[]{ clipboard });
}
@Override
@@ -213,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;
@@ -264,7 +246,7 @@ public class TerminalWidget extends AbstractWidget {
keysDown.clear();
// When blurring, we should make the last mouse button go up
if (lastMouseButton > 0) {
if (lastMouseButton >= 0) {
computer.mouseUp(lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1);
lastMouseButton = -1;
}

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

@@ -11,7 +11,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft;
@@ -21,7 +20,6 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
@@ -29,8 +27,6 @@ import net.minecraft.world.phys.HitResult;
import javax.annotation.Nullable;
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
private static final ModelResourceLocation ADVANCED_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced", "inventory");
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
@@ -42,13 +38,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
font = context.getFont();
}
public static ResourceLocation getTurtleModel(ComputerFamily family, boolean coloured) {
return switch (family) {
default -> coloured ? COLOUR_TURTLE_MODEL : NORMAL_TURTLE_MODEL;
case ADVANCED -> coloured ? COLOUR_TURTLE_MODEL : ADVANCED_TURTLE_MODEL;
};
}
public static @Nullable ResourceLocation getTurtleOverlayModel(@Nullable ResourceLocation overlay, boolean christmas) {
if (overlay != null) return overlay;
if (christmas) return ELF_OVERLAY_MODEL;
@@ -78,7 +67,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
var matrix = transform.last().pose();
var opacity = (int) (mc.options.getBackgroundOpacity(0.25f) * 255) << 24;
var width = -font.width(label) / 2.0f;
// TODO: Check this looks okay
font.drawInBatch(label, width, (float) 0, 0x20ffffff, false, matrix, buffers, Font.DisplayMode.SEE_THROUGH, opacity, lightmapCoord);
font.drawInBatch(label, width, (float) 0, 0xffffffff, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
@@ -96,10 +84,18 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
// Render the turtle
var colour = turtle.getColour();
var family = turtle.getFamily();
var overlay = turtle.getOverlay();
renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
if (colour == -1) {
// Render the turtle using its item model.
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper();
var model = modelManager.getItemModel(turtle.getBlockState().getBlock().asItem());
if (model == null) model = modelManager.getModelManager().getMissingModel();
renderModel(transform, buffers, lightmapCoord, overlayLight, model, null);
} else {
// Otherwise render it using the colour item.
renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, new int[]{ colour });
}
// Render the overlay
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);

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;
@@ -174,6 +173,7 @@ public final class LanguageProvider implements DataProvider {
// Metrics
add(Metrics.COMPUTER_TASKS, "Tasks");
add(Metrics.SERVER_TASKS, "Server tasks");
add(Metrics.JAVA_ALLOCATION, "Java Allocations");
add(Metrics.PERIPHERAL_OPS, "Peripheral calls");
add(Metrics.FS_OPS, "Filesystem operations");
add(Metrics.HTTP_REQUESTS, "HTTP requests");
@@ -271,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,47 +5,67 @@
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.computer.core.ComputerFamily;
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;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
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;
@@ -57,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)));
}
}
@@ -103,27 +120,23 @@ 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 nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
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())
.group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
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()),
new ResourceLocation(ComputerCraftAPI.MOD_ID, String.format("turtle_%s/%s/%s",
nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
))
add,
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
);
}
}
@@ -138,35 +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);
if (base.isEmpty()) continue;
var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
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())
.group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
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()),
new ResourceLocation(ComputerCraftAPI.MOD_ID, String.format("pocket_%s/%s/%s",
nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
))
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))
@@ -187,30 +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 = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
var group = "%s:turtle_%s_overlay".formatted(ComputerCraftAPI.MOD_ID, nameId);
var builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.REDSTONE, base.getItem())
.group(group)
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())),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_%s_overlays/%s".formatted(nameId, 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(" # ")
@@ -244,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#")
@@ -252,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).withExtraData(family(ComputerFamily.ADVANCED)),
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())
@@ -268,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#")
@@ -277,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).withExtraData(family(ComputerFamily.NORMAL)));
.buildOrThrow(TurtleRecipe::of)
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
.pattern("###")
.pattern("#C#")
@@ -288,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).withExtraData(family(ComputerFamily.ADVANCED)));
.buildOrThrow(TurtleRecipe::of)
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
.pattern("###")
.pattern("#C#")
@@ -299,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).withExtraData(family(ComputerFamily.ADVANCED)),
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())
@@ -358,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#")
@@ -366,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).withExtraData(family(ComputerFamily.ADVANCED)),
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())
@@ -436,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);
}
@@ -499,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 {
@@ -511,19 +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 Consumer<JsonObject> family(ComputerFamily family) {
return json -> json.addProperty("family", family.getSerializedName());
}
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;
@@ -58,7 +59,10 @@ class TagProvider {
tags.tag(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).addTag(BlockTags.WOOL).add(Blocks.COBWEB);
tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE).addTag(BlockTags.CAULDRONS).addTag(BlockTags.BEEHIVES);
tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE)
.addTag(BlockTags.BEEHIVES)
.addTag(BlockTags.CAULDRONS)
.add(Blocks.COMPOSTER);
// Make all blocks aside from command computer mineable.
tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add(
@@ -76,6 +80,8 @@ class TagProvider {
ModRegistry.Blocks.WIRED_MODEM_FULL.get(),
ModRegistry.Blocks.CABLE.get()
);
tags.tag(BlockTags.WITHER_IMMUNE).add(ModRegistry.Blocks.COMPUTER_COMMAND.get());
}
public static void itemTags(ItemTagConsumer tags) {
@@ -106,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

@@ -14,12 +14,15 @@ import dan200.computercraft.shared.computer.metrics.ComputerMBean;
import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher;
import dan200.computercraft.shared.util.DropConsumer;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
@@ -60,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();
}
@@ -111,4 +120,17 @@ public final class CommonHooks {
public static boolean onLivingDrop(Entity entity, ItemStack stack) {
return DropConsumer.onLivingDrop(entity, stack);
}
/**
* Add items to an existing creative tab.
*
* @param key The {@link ResourceKey} for this creative tab.
* @param context Additional parameters used for building the contents.
* @param out The creative tab output to append items to.
*/
public static void onBuildCreativeTab(ResourceKey<CreativeModeTab> key, CreativeModeTab.ItemDisplayParameters context, CreativeModeTab.Output out) {
if (key == CreativeModeTabs.OP_BLOCKS && context.hasPermissions()) {
out.accept(ModRegistry.Items.COMPUTER_COMMAND.get());
}
}
}

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;
@@ -34,8 +36,8 @@ import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
import dan200.computercraft.shared.computer.items.CommandComputerItem;
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;
@@ -139,19 +141,17 @@ public final class ModRegistry {
}
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = REGISTRY.register("computer_normal",
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), ComputerFamily.NORMAL, BlockEntities.COMPUTER_NORMAL));
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), BlockEntities.COMPUTER_NORMAL));
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = REGISTRY.register("computer_advanced",
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.COMPUTER_ADVANCED));
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), BlockEntities.COMPUTER_ADVANCED));
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command", () -> new CommandComputerBlock<>(
computerProperties().strength(-1, 6000000.0F),
ComputerFamily.COMMAND, BlockEntities.COMPUTER_COMMAND
));
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command",
() -> new CommandComputerBlock<>(computerProperties().strength(-1, 6000000.0F), BlockEntities.COMPUTER_COMMAND));
public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = REGISTRY.register("turtle_normal",
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), ComputerFamily.NORMAL, BlockEntities.TURTLE_NORMAL));
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), BlockEntities.TURTLE_NORMAL));
public static final RegistryEntry<TurtleBlock> TURTLE_ADVANCED = REGISTRY.register("turtle_advanced",
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.TURTLE_ADVANCED));
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.GOLD).explosionResistance(TurtleBlock.IMMUNE_EXPLOSION_RESISTANCE), BlockEntities.TURTLE_ADVANCED));
public static final RegistryEntry<SpeakerBlock> SPEAKER = REGISTRY.register("speaker", () -> new SpeakerBlock(properties().mapColor(MapColor.STONE)));
public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties().mapColor(MapColor.STONE)));
@@ -192,9 +192,9 @@ public final class ModRegistry {
ofBlock(Blocks.COMPUTER_COMMAND, (p, s) -> new CommandComputerBlockEntity(BlockEntities.COMPUTER_COMMAND.get(), p, s));
public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_NORMAL =
ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, ComputerFamily.NORMAL));
ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, () -> Config.turtleFuelLimit, ComputerFamily.NORMAL));
public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_ADVANCED =
ofBlock(Blocks.TURTLE_ADVANCED, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_ADVANCED.get(), p, s, ComputerFamily.ADVANCED));
ofBlock(Blocks.TURTLE_ADVANCED, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_ADVANCED.get(), p, s, () -> Config.advancedTurtleFuelLimit, ComputerFamily.ADVANCED));
public static final RegistryEntry<BlockEntityType<SpeakerBlockEntity>> SPEAKER =
ofBlock(Blocks.SPEAKER, (p, s) -> new SpeakerBlockEntity(BlockEntities.SPEAKER.get(), p, s));
@@ -264,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 {
@@ -311,7 +311,10 @@ public final class ModRegistry {
() -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
() -> ContainerData.toType(HeldItemContainerData::new, HeldItemMenu::createPrintout));
() -> ContainerData.toType(
HeldItemContainerData::new,
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand())
));
public static final RegistryEntry<MenuType<ViewComputerMenu>> VIEW_COMPUTER = REGISTRY.register("view_computer",
() -> ContainerData.toType(ComputerContainerData::new, ViewComputerMenu::new));
@@ -345,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 {
@@ -371,11 +374,11 @@ public final class ModRegistry {
public static final RegistryEntry<SimpleCraftingRecipeSerializer<ClearColourRecipe>> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::new);
public static final RegistryEntry<RecipeSerializer<TurtleRecipe>> TURTLE = REGISTRY.register("turtle", () -> TurtleRecipe.validatingSerialiser(TurtleRecipe::of));
public static final RegistryEntry<SimpleCraftingRecipeSerializer<TurtleUpgradeRecipe>> TURTLE_UPGRADE = simple("turtle_upgrade", TurtleUpgradeRecipe::new);
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serializer::new);
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serialiser::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PocketComputerUpgradeRecipe>> POCKET_COMPUTER_UPGRADE = simple("pocket_computer_upgrade", PocketComputerUpgradeRecipe::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PrintoutRecipe>> PRINTOUT = simple("printout", PrintoutRecipe::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<DiskRecipe>> DISK = simple("disk", DiskRecipe::new);
public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", ComputerUpgradeRecipe.Serializer::new);
public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", () -> CustomShapedRecipe.validatingSerialiser(ComputerUpgradeRecipe::of));
}
public static class Permissions {
@@ -464,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(ModRegistry.Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
CauldronInteraction.WATER.put(ModRegistry.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

@@ -4,8 +4,6 @@
package dan200.computercraft.shared.common;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.network.container.HeldItemContainerData;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.MenuProvider;
@@ -28,10 +26,6 @@ public class HeldItemMenu extends AbstractContainerMenu {
stack = player.getItemInHand(hand).copy();
}
public static HeldItemMenu createPrintout(int id, Inventory inventory, HeldItemContainerData data) {
return new HeldItemMenu(ModRegistry.Menus.PRINTOUT.get(), id, inventory.player, data.getHand());
}
public ItemStack getStack() {
return stack;
}

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

@@ -7,7 +7,6 @@ package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.common.IBundledRedstoneBlock;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockEntityHelpers;
@@ -42,13 +41,11 @@ import javax.annotation.Nullable;
public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntity> extends HorizontalDirectionalBlock implements IBundledRedstoneBlock, EntityBlock {
private static final ResourceLocation DROP = new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer");
private final ComputerFamily family;
protected final RegistryEntry<BlockEntityType<T>> type;
private final BlockEntityTicker<T> serverTicker = (level, pos, state, computer) -> computer.serverTick();
protected AbstractComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
protected AbstractComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
super(settings);
this.family = family;
this.type = type;
}
@@ -82,10 +79,6 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
protected abstract ItemStack getItem(AbstractComputerBlockEntity tile);
public ComputerFamily getFamily() {
return family;
}
@Override
@Deprecated
public int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
@@ -106,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);
@@ -124,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.
@@ -145,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);
}

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