1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2026-04-14 02:51:23 +00:00

Compare commits

...

321 Commits

Author SHA1 Message Date
Jonathan Coates
8be5c28484 Update to 26.1
- Replace vanilla-extract with Fabric Loom for common mod. Now that we
   no longer have to worry about remapping, this probably makes more
   sense.

 - Switch turtle upgrades to use ItemStackTemplate. Yay, immutability! I
   think I might change up the upgrade API here a bit, but not a blocker
   for release.

 - Lots of small things. I'm very glad for our game tests — these caught
   a fair few bugs.
2026-04-13 19:06:44 +01:00
Jonathan Coates
f31d0b20e6 Merge branch 'mc-1.21.x' into mc-1.21.y 2026-02-22 13:11:47 +00:00
Jonathan Coates
bed861fe7a Merge branch 'mc-1.20.x' into mc-1.21.x
Whoops!
2026-02-22 13:01:42 +00:00
Jonathan Coates
57a6add81a Bump CC:T to 1.117.1 2026-02-22 13:00:32 +00:00
Jonathan Coates
a2c8e5ec3c Merge branch 'mc-1.20.x' into mc-1.21.x 2026-02-22 12:56:26 +00:00
Jonathan Coates
46b688613d Bump CC:T to 1.117.1 2026-02-22 12:28:00 +00:00
Mariano Alipi
4bc04f1416 Update TurtleAPI documentation for block breaking (#2380)
Clarified the description of block breaking capabilities for mining turtles.
2026-01-30 19:52:43 +00:00
Jonathan Coates
a0571e444c Fix getResponseHeaders @since version
Closes #2378
2026-01-28 23:56:53 +00:00
Meme Tech
7292e3298f Fix discrepancy in type help (#2374) 2026-01-28 10:38:26 +00:00
Jonathan Coates
46f5dc485e Fix more issues caused by Java 25 update
- Bump CC:T Javadoc version, to fix issues with dropping newlines when
   converting to Markdown. I'd originally planned to switch to
   Markdown-style Javadocs, but tooling is stil a bit lacking
   (Intelli's formatting of @params is entirely broken for instance).

 - Force more recent ASM version, to allow Forge to run.
2026-01-28 09:24:52 +00:00
Jonathan Coates
f25f391b41 Bump Fabric loader version
Required for Fabric tests to work. Yeah, I should have tested this
before pushing :D.
2026-01-26 23:15:35 +00:00
Jonathan Coates
d5e45b65bf Disable "disableRecompilation" in CI
Hadn't realised that a MDG version bump started enabling this in CI.
Ughgr, wish people didn't change behaviour based on environment
variables, makes it harder to test.
2026-01-26 22:54:13 +00:00
Jonathan Coates
4a6ec54813 Remove "CC may be installed incorrectly" message
It /technically/ may be the case, but in the days of modern mod loaders,
much less likely. Normally this is user error.

Closes #2372.
2026-01-25 09:39:13 +00:00
Jonathan Coates
c98b99863d Update some build tooling
Mostly now use JDK 25 to build, to allow us to use markdown docstrings
in the future — this makes the javadoc generator *much* simpler.
2026-01-25 09:39:13 +00:00
UQuark
419441164d Fix #2355 (#2367)
If a fluid uses NBT tags, then when we construct our NBT-less
FluidStack, then the fluid does not match and will not be transferred.
Instead, we search the source tank for a matching FluidStack, and use
that directly.

This is a bit limiting if a tank contains multiple versions of the fluid
with different NBT, but hopefully that's not too common.

Fixes #2355
2026-01-17 20:49:56 +00:00
Jonathan Coates
b7d1d9d012 Limit lenght of sound name
Fixes #2366
2026-01-17 20:15:28 +00:00
Jonathan Coates
2105c5c13f Don't materialise the whole split list
This should avoid allocating slightly less memory *in some cases*. I
dare say there's still ways to OOM here. But also, if you want to OOM
the server, you don't need to place a sign to do it!

Closes #2365.
2026-01-17 20:03:41 +00:00
Jonathan Coates
06d1abfeca Mention turtle.craft(0) in the docs
Fix the range of the limit parameter, and mention that it can be used
for checking whether a recipe is valid.
2026-01-04 13:05:59 +00:00
Jonathan Coates
23985ef41f Remove MoreRed integration
Post 1.21.1, MoreRed switched to using ExMachina[^1] for handling
bundled redstone connections, meaning our existing integration code
crashed. While the changes seem really cool, the documentation is
lacking, and I just haven't got the spoons to puzzle through it all. For
now, let's just remove support — PRs very much welcome to add it back!

Closes #2309

[^1]: https://github.com/Commoble/exmachina
2025-12-28 12:43:20 +00:00
Jonathan Coates
0005ee9657 Add tests for consistent getItemDetails behaviour
Closes #2346.
2025-12-28 11:01:57 +00:00
Jonathan Coates
b481aa95f2 Merge branch 'mc-1.21.x' into mc-1.21.y
There's definitely some more work to be done here — I need a datafixer
to move pocket upgrades from the bottom to the top — but it otherwise
seems to work.
2025-12-24 19:06:14 +00:00
Jonathan Coates
17867b5d8b Merge branch 'mc-1.20.x' into mc-1.21.x 2025-12-24 09:16:38 +00:00
Jonathan Coates
d5e0b0ad2a Bump CC:T to 1.117.0
A day early, but my commitment to the bit is waning.
2025-12-24 09:07:02 +00:00
Jonathan Coates
24fd27d2a3 Add test for potion durations 2025-12-24 08:51:15 +00:00
Jonathan Coates
446b6772a9 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-12-22 09:58:02 +00:00
Jonathan Coates
90e7307fb4 Fix websocket_closed not always being queued on failure
- Reorganise the HTTP test code to make it a bit more extensible. Add
   support for sending messages to connected websockets.
 - Provide a friendlier message for too-large-payload errors.
 - Return failure reason from Websocket.receive

Fixes #2149.
2025-12-19 21:12:37 +00:00
Jonathan Coates
1520bebb6c Simplify event code in LuaTaskContext
We remove support for multiple event listeners, and now just use a
simple event queue again. This makes the code a little simpler, and
removes the risk of race conditions where we do do something, and it
queues the event before we call pullEvent().
2025-12-19 21:12:31 +00:00
Jonathan Coates
419d823d3b Update Redstone in onNeighborChange
We removed onNeighborChange in 676fb5fb53,
on the basis that this was no longer needed for peripheral updates.
However, it *is* required for redstone updates, as MoreRed does not
trigger any block updates for bundled cables.

Fixes #2316.
2025-12-18 15:06:01 +00:00
Jonathan Coates
f820cd8b43 Rewrite the GPS setup guide
Mostly trying to avoid the number of asides, to make it a bit more
straightforward. I'm not entirely sure I succeeded.

Fixes #1681, closes #1542.
2025-12-18 13:30:12 +00:00
Jonathan Coates
4b1541154b Add Websocket.getResponseHeaders method
Closes #1387
2025-12-17 23:11:12 +00:00
Jonathan Coates
8a1a545ab1 Add reference for item details
Also change potion display name to include potency, to match
enchantments.
2025-12-17 18:27:45 +00:00
Jonathan Coates
4e9f3501b0 Fix usage of deprecated Netty APIs in tests 2025-12-17 14:04:49 +00:00
Jonathan Coates
7659c608a6 Dynamically pick the port in HTTP tests 2025-12-17 14:03:38 +00:00
Jonathan Coates
309b78eb8a Update JEI to MC 1.21.11 2025-12-17 13:14:14 +00:00
Jonathan Coates
a8032d6c65 Remove jzlib
Netty now no longer advertises it supports custom compression windows
when jzlib is not available[^1], so we no longer need this.

[^1]: 778ba3e54e
2025-12-17 13:12:31 +00:00
Jonathan Coates
a072b116fa Update to 1.21.11
90% just ResourceLocation → Identifier. Also:
 - StandaloneModel now needs to detect which atlas to use, as the block
   and item atlases are different.

 - MultiLineLabel now always draws in white with a drop shadow, so we go
   back to manual word-wrapping for now.
2025-12-17 11:34:12 +00:00
Jonathan Coates
778805a8d8 Add reference for block details
I do not like the flow of this page, but did not know how better to
structure it.

We really need a way to write things which use the same type syntax as
parameters. I don't like how this (and events!) are formatted so
differently. It'll do for now though.
2025-12-16 23:13:21 +00:00
Jonathan Coates
f31d8febbf Handle more of Windows's weird paths
AFAICT, any sequence of ".[ .]*" (except from "") is equivalent to "."
on Windows. Let's handle that in our path sanitisation code.

Fixes #2151.
2025-12-15 23:48:54 +00:00
Jonathan Coates
60bcb9d4d3 Add potion effects to item details
There's some nasty hacks here around potion durations on other items.
This should get a bit cleaner in newer versions of the game, once we
switch to data components.

Fixes #2266.
2025-12-15 22:16:53 +00:00
Jonathan Coates
1ea84fe7d7 Clear turtle player inventory when syncing
Fixes #2332.

I *think* what's going on here is:
 - When we place a block with the turtle, we copy items between the
   turtle's inventory and the fake player's inventory. We *don't* clear
   items from the fake player's inventory after placing, as we'll do it
   next time we use the fake player.

 - At the start of the next tick, Stack Refill then loops over the
   fake player's inventory and moves some items around. However, because
   we just set the items directly (rather than using .copy()), this
   mutates the items in the turtle's inventory too!

To fix this, we just clear the player's inventory after finishing with
it, so Stack Refill never sees these items.
2025-12-15 20:58:01 +00:00
ShreksHellraiser
a2ed5c385c Add MapColor metadata provider for block and item details (#2164) 2025-12-15 20:33:42 +00:00
Jonathan Coates
9ad3b03946 Let pocket computer on lecterns wrap the peripheral below
- Shuffle some of the accessors in PocketComputerItem around, to make
   them easier to use in the lectern code.

   I still don't think we've got a good interface here. We could
   possibly move more of the update code into PocketBrain, and just have
   PocketComputerItem responsible for syncing brain<->stack
   state. Something to fiddle with, but not sure it would be any
   cleaner.

 - Set the bottom peripheral when the pocket computer is placed in the
   lectern (and on block updates, etc...), and clear it when removed.

   Again, the code here is uglier than I would like (particularly with
   all the instanceofs). I did wonder about having some LecternBehaviour
   class, which holds the printout/pocket-specific state, but there's so
   many places we need to hook in (and they're so different between the
   two), I'm not sure it's worth it right now.

Fixes #2126, closes #2223.
2025-12-15 19:22:40 +00:00
Jonathan Coates
5f704b2c58 Test for relay initial input 2025-12-15 15:45:11 +00:00
Jonathan Coates
53509b0d82 Revert disableRecompilation
This doesn't apply parchment mappings, and so parameters are all named
"o". This confuses OverridingMethodInconsistentArgumentNamesChecker, so
let's ignore this for now.
2025-12-15 15:03:52 +00:00
Jonathan Coates
013f2e500f Refresh relay input/output on load
We now forcibly update all redstone inputs/outputs when the relay is
first placed.

It's not entirely clear to me if this is the right solution. The
alternative one would be to persist the redstone state instead. However,
most peripherals do *not* do this (e.g. monitor, speaker), so let's
match that for now.

Fixes #2175.
2025-12-15 14:47:27 +00:00
Jonathan Coates
6702ab8f9d Enable TeaVM WASM GC Backend
I'd tried this ages ago, and had some issues I utterly failed to find a
minimal reproducer for. Now it Just Works.
2025-12-15 14:17:03 +00:00
Jonathan Coates
16d934265a Update TeaVM version
We no longer require any patches against TeaVM. Still shipping our own
for now, until 0.14 is stable.

<3 konsoletyper
2025-12-15 14:07:11 +00:00
Jonathan Coates
ff9f038275 Another build tooling bump
- Update to latest MDG, to avoid decompiling Minecraft in CI.
 - Fix several Gradle warnings.
 - Bump ErrorProne, and fix a few more nits.
2025-12-15 12:42:38 +00:00
Jonathan Coates
1f5339c86e Avoid using TickScheduler unless needed
Fixes #2190, or at least works around the problems in BuildingGadgets
enough.
2025-12-14 22:30:09 +00:00
Jonathan Coates
28e569f2b4 Support mouse events for lectern pocket computers
Ideally we'd be able to support right clicking on anything when we have
a NoTermComputerScreen open. However, that's incredibly awkward to do
(see c45fc94752, and discussion in #2160),
so instead we create a new PocketComputerLecternScreen, which handles
mouse events.

We need to do some awkward converting the player's position and look
vector into the terminal's coordinate space, and then projecting it onto
the terminal screen, but otherwise this is fairly straightforward.

Closes #2330.
2025-12-14 17:22:15 +00:00
Jonathan Coates
28682aa468 Rewrite computer input handling (again)
Hey, it lasted almost a year!

Computer input is somewhat stateful, as we need to track things like
which key(s) are currently held, and what the last mouse button/position
was. This code is currently duplicated in several places (specifically
TerminalWidget, ServerInputState and the standalone emulator).

In order to implement lectern pocket computer mouse interactions, we'd
have to duplicate this logic once again. Instead, we move this code into
a common class.

 - Replace the InputHandler interface with a simpler ComputerInput one
   (this no longer has computer actions, like reboot or terminate). This
   interface never made much sense (aside from hiding implementation
   details), as code only ever consumed a single implementation of it.

   On the client, this requires a new "ClientComputerActions" class.
   This feels a bit clunky to me, but it's simple and it works.

 - Replace ComputerEvents with a EventComputerInput class (terrible
   name, I know!), which queues events on a computer.

 - Move common input state tracking and validation into a single
   UserComputerInput class, which wraps an existing ComputerInput. This
   is used by both the terminal widget, and the server-side input state.
2025-12-14 16:53:18 +00:00
Jonathan Coates
1ad20eea05 Pass registries to details providers
In 634cd52b60 we found out that NBT
hashing requires access to the current registries. However, passing that
around would have required an API change, so we just probed the current
server globally.

On 1.21.10 we can break the API, so let's do this properly:
 - DetailRegistry/DetailProvider now take a HolderLookup.Provider as an
   argument. I did debate whether non-basic details should use a
   MinecraftServer for now, but that makes the API less uniform.

 - This means our generic peripherals need access to the current server.
   We also update the API to support registering
   MinecraftServer->GenericSource factories
2025-12-09 23:57:23 +00:00
Jonathan Coates
2a35d17906 Clarify behaviour around drawImage cursor 2025-12-09 23:15:45 +00:00
Jonathan Coates
9c9393c104 Merge branch 'mc-1.21.x' into mc-1.21.y 2025-12-09 22:27:22 +00:00
Jonathan Coates
634cd52b60 Encode stack components with RegistryOps
If we don't do this, then encoding may fail, resulting in an empty NBT
tag. We currently don't pass the current registries to ItemDetails (will
fix on 1.21.10+), so awkwardly have to store it as a global for now.

Fixes #2340
2025-12-09 22:23:35 +00:00
Jonathan Coates
86018f9464 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-12-09 19:59:20 +00:00
Jonathan Coates
d39c91b25a Update build tooling 2025-12-09 17:52:18 +00:00
Tomodachi94
61e9626302 Retroactively document changes to os.version() (#2323) 2025-11-23 13:49:55 +00:00
JackMacWindows
307bebd5d2 Fix missing return value docs in shell.openTab (#2333) 2025-11-22 08:16:58 +00:00
Jonathan Coates
6334a9df15 Clarify which sounds can be played with speaker
Closes #2327
2025-11-18 19:23:14 +00:00
Jonathan Coates
08d12f2b0e Remove Fabric config patcher 2025-11-09 09:37:26 +00:00
Jonathan Coates
fbdf0d677f Sync translations from Crowdin 2025-11-09 09:23:59 +00:00
Jonathan Coates
52b127fcc7 Initial update to 1.21.10 (#2304)
The server side changes are pretty straightforward:

 - Level.isClientSide is now a method.
 - NeoForge has switched to a Fabric-style item/fluid storage API. This
   is a fairly simple migration, as we've got support for this already!

   This does mean that we need a wrapper type here too. We really should
   support generics in the generic method system :/.

Unfortunately, this is another rough update on the rendering front. The
main change here is that BE renderers now a) have separate extract/render
phases and b) "draw" to a scene graph (SubmitNodeCollector) rather than
directly.

 - Update lectern models to use the model/layer system, rather than
   constructing model parts directly. We don't *need* to do this, but
   makes this a bit more consistent with vanilla.

 - Remove TurtleUpgradeModel.renderForLevel. I could not figure out a way
   to make this look nice with the separate extract/render phases. This
   is made worse by the fact that CC:T doesn't actually use this
   anywhere, so not sure what we actually need! Something to bring back
   in the future, if other people need it.

 - The turtle/monitor F3 info now uses vanilla's debug renderer system.
   We could maybe make the monitor one use a gizmo in future updates, so
   it renders coordinates in-world instead. Not sure.

Unfortunately, some bits are entirely broken right now:

 - Breaking progress for turtles. There's no way to do custom breaking
   progress, so we just render this as normal custom geometry, which has
   all sorts of depth-buffer issues.

 - Monitors do not use a VBO (again, no way to do this), and render every
   frame. This means monitors are pretty slow to render right now (one or
   two is fine, but monitor arrays are unusable). Will need to submit PRs
   to the various mod loaders for this.
2025-11-02 20:24:09 +00:00
Jonathan Coates
cb0e868471 Merge branch 'mc-1.21.x' into mc-1.21.y 2025-11-02 15:25:15 +00:00
Jonathan Coates
5f87984bac Merge branch 'mc-1.20.x' into mc-1.21.x 2025-11-01 19:47:39 +00:00
Jonathan Coates
18f3426f1d Bump CC:T to 1.116.2 2025-11-01 19:40:52 +00:00
Jonathan Coates
0ec46fe38e Update CI to use Java 21 2025-10-31 22:27:38 +00:00
Jonathan Coates
569de7fafb Update to Gradle 9.2
- Update to Loom 1.12. This requires Java 21, so we now build with Java
   21, but targetting 17. The new "-release" flag does make this much
   easier, so hopefully shouldn't cause too many issues.

 - Bump versions of a lot of other things.

 - Fix various errorprone/checkstyle grumbles.
2025-10-31 19:50:44 +00:00
Jonathan Coates
157ce4fa55 Update Create for Fabric
Awww no, here we go again.

The Forge/Fabric versions are identical again, but keeping them separate
for now, as I'm a) lazy and b) not convinced they won't diverge again.

Fixes #2311
2025-10-26 12:39:08 +00:00
Jonathan Coates
b9ed66983d Fix some isues in SNBT parsing
- Accept the full range of unquoted strings
 - Fix error when failing to parse an unquoted string

See #2277. This is not sufficient to close the issue (wow, there's so
much more wrong with the code), but at least stops unserialiseJSON
crashing.
2025-08-31 12:11:34 +01:00
Jonathan Coates
d0fbec6c6b Add bounds checks for compartment lookup
This *shouldn't* be needed, as the compartment index is always >= 0, and
Inventory.getItem returns an empty stack if the item is not found.

However, some mods pass a negative compartment index, which causes
getItem to throw an AIOOB instead.

Fixes #2267
2025-08-28 08:10:35 +01:00
Zirunis
a683697e8c Fixed two typos in dfpwm.lua (#2272) 2025-08-27 06:47:42 +01:00
Zirunis
9e233a916f Fixed typo in docstring of textutils.serializeJSON (#2260) 2025-08-08 10:45:46 +01:00
Jonathan Coates
5a9e21ccc3 Clarify behaviour of itemGroups field
The availability of this field changes between Minecraft versions, but we
didn't make that entirely clear.

See #2247
2025-07-20 11:16:39 +01:00
Jonathan Coates
5f16909d4b Remove empty-argument optimisation
This doesn't work with getTableUnsafe, as empty arguments are considered
closed already. We could argubly special-case the empty args, but the
optimisation has very minor benefits, so I don't think worrying about too
much.

Fixes #2246.
2025-07-19 22:24:32 +01:00
Jonathan Coates
4cccf1817c Update to Minecraft 1.21.8 2025-07-18 17:04:28 +01:00
Jonathan Coates
b8d9499027 Fix terminals not receiving scroll events
Minecraft 1.21.2 added a mouseScroll override to
AbstractContainerScreen, which means that child widgets no longer
receive scroll events. We reimplement that logic in our computer screen.

Fixes #2245.
2025-07-17 09:11:26 +01:00
Jonathan Coates
00475b9bb0 Support LuaTable arguments in @LuaFunction 2025-07-14 08:11:35 +01:00
Jonathan Coates
e81a2c72ce Merge branch 'mc-1.21.x' into mc-1.21.y 2025-07-12 21:46:28 +01:00
Jonathan Coates
01de6110c6 Re-enable JEI integration 2025-07-12 21:41:50 +01:00
Jonathan Coates
9cf0f85fcb Merge branch 'mc-1.20.x' into mc-1.21.x 2025-07-12 19:00:00 +01:00
Jonathan Coates
018ce7c8a5 Bump CC:T to 1.116.1 2025-07-12 18:57:05 +01:00
Jonathan Coates
44726827b4 Sync translations from Crowdin 2025-07-12 09:49:14 +01:00
Jonathan Coates
2bf0aba455 Don't use SpeakerPosition to get speaker's level
We use getLevel() specifically for reading the current registries
(and/or server). We don't need the exact position of the speaker to
query this, so add a dedicated method for it.

We actually had a similar method on 1.21.7 already for upgrades. This
just moves it to SpeakerPeripheral.

Fixes #2236.
2025-07-10 01:07:51 +01:00
Jonathan Coates
3cf914cb4c Fix NPE when loading mcfunctions
CommandSourceStack.getServer can be null, despite being marked as
non-nullable. Mojang!!! *shakes fist*

Fixes #2235.
2025-07-10 00:47:42 +01:00
Jonathan Coates
4868c4aa32 Small bits of cleanup
- Remove unused MonitorRenderer. I'm sure this had been deleted
   already, but apparently not!
 - Add missing items to the changelog.
 - Fix crash when clearing tests in a world.
 - Bump Iris deps, to help with debugging #2229.
2025-07-10 00:29:06 +01:00
Batári Balázs László
180156ff1c Add lang hu_hu (#2232) 2025-07-06 21:47:22 +00:00
Jonathan Coates
c6ba753568 Update allowed Minecraft versions 2025-07-06 22:34:43 +01:00
Jonathan Coates
9f45c91925 Update to latest TeaVM 2025-07-02 09:21:43 +01:00
Jonathan Coates
67412a2b72 Update to Minecraft 1.21.7 2025-06-30 22:55:55 +01:00
Jonathan Coates
5fa724ed24 Move Create integration setup to its proper place
Merge conflict gone wrong, I assume.
2025-06-30 22:53:15 +01:00
Jonathan Coates
76869593f0 Merge branch 'mc-1.21.x' into mc-1.21.y 2025-06-28 11:31:32 +01:00
Jonathan Coates
fbf994e803 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-06-28 11:21:22 +01:00
Jonathan Coates
8344c0a5c2 Bump CC:T to 1.116.0 2025-06-28 11:03:52 +01:00
Jonathan Coates
a292d33830 Syntax highlighting for multiline tokens in edit
I don't love the implementation of this (see discussion in #2220), but
it's better than nothing. Wow, the editor really needs a bit of a
rewrite, the code is kinda messy.

Fixes #1396.
2025-06-25 22:50:23 +01:00
Jonathan Coates
341d1c7bc2 Move paint/edit menu bar into common module
I want to add a menu bar to the edit runner too, so let's make this code
a little more reusable first.
2025-06-25 22:14:09 +01:00
Jonathan Coates
846f9dff03 Update to latest NeoForge
Welllll, latest when I wrote the code, not at actual time of writing.
2025-06-24 22:59:44 +01:00
Jonathan Coates
64d10ad45b Data fixers for turtle owners 2025-06-24 22:58:04 +01:00
Jonathan Coates
531eacfac7 Merge pull request #2224 from matematikaadit/patch-1
Miniscule typo fix in the shell.path() doc comment
2025-06-21 16:59:01 +01:00
Adit Cahya Ramadhan
1f3da5205c Miniscule typo fix in the shell.path() doc comment
Noticed this when reading the shell API page in the wiki.
2025-06-21 22:29:04 +07:00
Jonathan Coates
e3fecb013a Update to Minecraft 1.26.1
Still one TODO left, but around data fixers, so fairly small.

 - GUI rendering got a big overhaul. I avoided the worst of it with
   9272e2efcd, but things like terminals
   and printouts still require some custom rendering.

 - Item models are now de-duplicated when rendering in the UI, so we
   need to keep track of their identity with
   (appendModelidentityElement). I'm not sure I've got this entirely
   right — whole thing feels unfortunately error-prone.

 - BE serialisation now goes through a Value{Input,Output} class, rather
   than using NBT directly. Fairly simple change, but has changed the
   format of the GameProfile used in turtle's owners. Need a DFU patch
   for this.
2025-06-18 21:19:04 +01:00
Jonathan Coates
798ceefafe Add test for crafting of disks
See #2221.
2025-06-18 21:00:32 +01:00
Jonathan Coates
7c0f79fc3c Move edit_runner into its own module
I've a few more features I'd like to add to it. Moving it out makes it
slightly easier to maintain.
2025-06-17 17:55:24 +01:00
Jonathan Coates
b35cefc5dd Switch from path to parentPath
I've been putting this off for a while, as I had issues in the past with
people using old Node versions (e.g. #1806), but it no long works on my
machine, so time to make the switch.

Also do a bit of a package update. Hit a rollup bug while doing this
(https://github.com/rollup/plugins/issues/1877), so holding that update
back for now.
2025-06-17 17:46:10 +01:00
Jonathan Coates
ec3dd328b3 Construct ByteBuffer manually in monitor renderer
In the 1.21.4 update (9277aa33e9), we
removed our DirectVertexBuffer class, and switched to vanilla's
VertexBuffer. This forced us to use MeshData and thus
ByteBufferBuilder instead of allocating the ByteBuffer ourselves.

One thing I'd missed with this is that Iris's text vertex sink API
requires us to allocate the whole buffer up-front and so the resulting
buffer has a limit of the *maximum* number of vertices rendered, not the
actual one.

This wasn't an issue on 1.21.4, as we didn't check this (I guess we just
silently rendered junk??), but for the 1.21.5 update
(a1df196673) we added some extra
assertions here, which now fail on Iris.

Typically, the whole original change was is now entirely redundant, as
Vanilla has removed VertexBuffer entirely, and so we can/should work in
terms of raw ByteBuffers again.

Fixes #2219.
2025-06-15 18:04:52 +01:00
Jonathan Coates
f3f43191ab Merge branch 'mc-1.21.x' into mc-1.21.y 2025-06-15 16:59:48 +01:00
Jonathan Coates
89dd521930 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-06-15 16:31:51 +01:00
Jonathan Coates
9272e2efcd Use vanilla's nine-slice sprites for border rendering
- Move remaining sprites to the vanilla GUI atlas.

 - Convert our computer border/sidebar sprites to use vanilla's
   nine-sliced mcmeta files. I thought I'd have to do something custom
   here for the sidebar, as that has no right border, but vanilla
   supports that natively!

 - Use the normal GuiGraphics.blitSprite for rendering computer
   border/sidebar.

 - Obey nine-slice scaling within the pocket computer renderer.
2025-06-15 16:25:54 +01:00
Jonathan Coates
69353a4fcf Use lexer for edit's syntax highlighting
This is slightly more accurate for long strings and comments. Note that
we still work a line at a time (and in a non-incremental manner), so
doesn't actaully support multi-line strings (#1396).

We do now treat goto as a keyword (fixes #1653). We don't currently
support labels — those *technically* aren't a token (`:: foo --[[ a
comment ]] ::` is a valid label!), but maybe we could special-case the
short `::foo::` form.
2025-06-15 13:25:21 +01:00
Jonathan Coates
ff363dca5a Move sidebar_advanced.png down by one pixel
Apparently this has been broken since the file was created in
53546b9f57d9acaa4cdca14ae00eaf68ce8c50bd!? I'm sure I fixed this before,
but maybe that was a different but similar issue >_>.
2025-06-14 18:44:16 +01:00
LorneHyde
1c51282426 Fix syntax highlighting for strings ending in an escaped backslash (#2194) 2025-06-08 19:55:14 +00:00
Jonathan Coates
4a3a1c9275 Merge pull request #2214 from Wojbie/patch-1
Update motd path in startup.lua
2025-06-03 08:08:30 +01:00
Wojbie
2557dd0af9 Update motd path in startup.lua
Removes situations where shell resolution caused arbitrary program called `motd` at root get executed instead of expected /rom one.
2025-06-03 01:03:02 +02:00
Jonathan Coates
b5c0c6e104 Fix out-of-bounds when pasting too-long text
Used a `<=` instead of a `<`! How did I mess this up!?

Fixes #2209
2025-06-02 08:58:43 +01:00
SpartanSoftware
876fd8ddb8 Fix 0 being treated as a valid colour (#2211) 2025-05-31 07:46:03 +00:00
Jonathan Coates
ee3b1343b5 Handle keyboard layouts for our computer shortcuts
Convert GLFW's key codes back to their actual key, and then use that
when checking keyboard shortcuts. We *don't* do this for the paste key,
just to be consistent with vanilla's behaviour.

Fixes #2207.
2025-05-25 22:32:39 +01:00
JackMacWindows
b440b964b7 Add notes about minor changed file handle behavior in 1.109.0 (#2203) 2025-05-25 20:24:26 +00:00
Jonathan Coates
5dfc401b45 Update build plugin versions 2025-05-18 10:05:27 +01:00
Jonathan Coates
0790a8346a Merge branch 'mc-1.21.x' into mc-1.21.y 2025-05-16 18:38:21 +01:00
Jonathan Coates
418c9be7ac Allow changing terminal size with components
We add a new computercraft:term_size component that allows changing the
terminal size of computers and pocket computers.
2025-05-16 18:22:20 +01:00
Jonathan Coates
b491f6b11f Merge branch 'mc-1.20.x' into mc-1.21.x 2025-05-16 17:56:51 +01:00
Jonathan Coates
acafc06449 Make turtle upgrade models data driven
It's ItemModel, but for upgrades!
2025-05-11 09:17:54 +01:00
Jonathan Coates
598fd98a8b Load turtle overlays during model loading
Back in f10e401aea, we changed turtle
overlays to be loaded as a dynamic registry. This solved some of the
problems we had with upgrades (elf-compatibility), and seemed like a
good idea at the time.

However, because overlays are part of datapacks (not resource packs), we
also needed support for loading overlay models, which we did via an
"extra_models.json" file.

With the ItemModel changes, we can go for a different approach:
 - Turtle overlays are now stored on the item/BE as a simple
   ResourceLocation again.

 - The TurtleOverlay class is moved to the client, and loaded from
   resource packs, during model loading. They're now split into a baked
   form (holding a StandaloneModel) and an unbaked one (holding the
   ResourceLocation).

 - extra_model.json is no longer supported.
2025-05-10 23:30:39 +01:00
Jonathan Coates
e13e8ff92e Fix a couple of rendering issues
- Fix printouts in item frames being rendered too far foward.
 - Fix wired modems being flipped.
2025-05-10 17:52:12 +01:00
Jonathan Coates
0a0c80db41 Allow equipping pocket computers on the bottom
This allows equipping pocket computers on both the back (as before) and
bottom of a pocket computer. The asymmetry is a little unfortunate here,
but makes some sense with the crafting recipe (above goes behind, below
goes on the bottom).

 - Move some functionality from IPocketAccess into a PocketComputer
   interface (and PocketComputerInternal) interface, used by the pocket
   API.

   IPocketAccess preserves the same interface as before. Unlike
   ITurtleAccess, we /don't/ expose the PocketSide in the public API.

 - Several pocket-computer methods (e.g. setUpgradeData, setColour) are
   now required to be called on the main thread, and when the computer
   is being held. This allows us to write back changes to the item
   immediately, rather than the next time the item is ticked.

   Sadly this doesn't actually remove the need for onCraftedPostProcess
   as I'd originally hoped, but I think does make the code a little
   simpler.

 - Rename "computercraft:pocket_computer" component to
   "computercraft:back_pocket_computer".

 - And finally, support multiple upgrades on the pocket computer. This
   is actually quite an easy change, just tedious — there's lots of
   places to update!

Fixes #1406, and I think fixes #1148 — you can use a speaker to notify
you now.
2025-05-10 17:15:41 +01:00
Jonathan Coates
4344c3072f Add a test for turtle upgrade crafting
Every few years I get confused about which side turtle upgrades go on
when crafting. The fact that it's flipped always throws me! Let's add a
comment to the recipe, and add some tests to reassure myself.
2025-05-10 14:39:51 +01:00
Jonathan Coates
c20336286b Update to FAPI 0.122
- Use new tooltip component registry. This means we can move the
   tooltip appending magic to Neo only.

 - Use new chunk level change event. I'm not actually sure if we need
   this on recent versions (I can't reproduce the monitor update bug
   that we introduced this to fix), but I've no clue what's changed here.
2025-05-02 19:01:26 +01:00
Jonathan Coates
356366ede8 Undo dev changed to build script 2025-04-30 22:33:22 +01:00
Jonathan Coates
a1df196673 Update to Minecraft 1.21.5
0/10, would not recommend. Though increasingly feeling that about
modding as a whole — really not feeling as emotionally rewarding as it
once did.

Server-side changes are well, not simple, but relatively straightforward:

 - Block removal code is now called before the BE is removed, not after.

   - Monitors now need to track if they've being removed or not again.

   - Turtle drop consuming code no longer tries to insert items into
     the turtle immediately, instead waiting 'til the action is
     complete. Otherwise if the turtle gets destroyed mid-action
     (e.g. the block explodes), then it tries to insert its drops into
     itself!

     We previously guarded against this by checking if the turtle BE had
     been removed, but obviously this no longer works, so just easier to
     shift the insertion.

  - The interface for reading/writing NBT has been overhauled. It has
    native "getOr" and codec support (nice!) but also has been changed
    again in the latest snapshot (less nice!).

 - The dye item component no longer has a "hide tooltip" flag. We now
   hide the tooltip with a default component instead.

 - Related to the above, we can now do all the tooltip-related things we
   needed to do with vanilla's TooltipProvider. This did require
   splitting NonNegativeId into subclasses for disk/computer, but
   otherwise is quite nice.

 - Some changes to model datagen. Annoying, but boring.

 - Game tests got a complete overhaul. I'm keeping the interface the
   same for now (@GameTest), because I'm blowed if I'm datagenning test
   instances :p. If it's any consolation, both NF and Fabric are doing
   this too.

Client changes are a bit more involved though:

 - VertexBuffer has been entirely removed. We now construct the
   GpuBuffer directly.

 - BakedModel is gone! Oh this caused so much suffering for turtle
   models. I ended up rewriting the whole system in processes (which
   then involved PRs to NF and Fabric). Rather than returning a
   TransformedModel, turtle models are now responsible for rendering the
   model.

   This may see another rewrite in the future. I'd like to switch to
   JSON-based turtle models (like item models), but that's blocked on
   some changes to NF right now.

   Sorry to all add-on devs, I know this is a big change.
2025-04-30 22:31:30 +01:00
Jonathan Coates
947001104d Update Cobalt to 0.9.6
- Allow heterogenous __lt/__le.
2025-04-21 14:36:44 +01:00
Jonathan Coates
8711512769 Remove allocation tracking for computers
Reverts 76968f2f28. We'd originally added
this to gather some numbers for #1580, with the hope that it would also
be useful for server admins. Sadly, it's not as accurate as I originally
hoped — the number sometimes goes down for unclear reasons (something to
do with the TLAB maybe??).

Closes #1739.
2025-04-21 08:32:03 +01:00
Jonathan Coates
a939ad8b97 Merge branch 'mc-1.21.x' into mc-1.21.y 2025-03-25 08:45:03 +00:00
Jonathan Coates
fdae94b3c1 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-03-25 08:44:27 +00:00
Jonathan Coates
9c0ce27ce6 Switch a few more places to use Java 17 features
New ErrorProne hint, and one which is actually pretty useful!
2025-03-22 09:39:47 +00:00
Jonathan Coates
c458360b18 Bump versions of build tooling
The main thing of note is Spotless, which also bumps the version of
Ktlint. I've been putting this off for a while[^1], as this changed a
bunch of formatting, and Spotless's (broken) caching was making it hard
to test. Ended up downloading ktlint and running it localy.

[^1]: 8204944b5f
2025-03-21 14:28:31 +00:00
Jonathan Coates
09ad6c1905 Add tags for disks and floppies
Fixes #2158
2025-03-20 19:16:16 +00:00
Jonathan Coates
0e1e8a72d3 Fix syncing of colour between PocketBrain and item
- Actually set colour when constructing the brain.
 - Sync it back after crafting, much like we do for upgrades (see
   dcc74e15c7) for more details.

We should take a proper look at this on 1.21.4 and make these methods
main-thread only, so we can sync immediately.

Fixes #2157
2025-03-20 18:54:06 +00:00
Jonathan Coates
995a6e7379 Merge branch 'mc-1.21.x' into mc-1.21.y 2025-03-18 09:29:35 +00:00
Jonathan Coates
ffa6eadc26 Register our BEs as entities
Fixes #2141. Hah, I wrote some tests for this in
b03546a158, but they pass because hoppers
still support vanilla inventories, but turtles don't.

Wish NeoForge registered a fallback for any inventory, like Fabric does,
but there we go.
2025-03-16 16:37:29 +00:00
Jonathan Coates
7c1e8e1951 Fix usages of javax's Nullable annotation 2025-03-16 16:29:19 +00:00
Jonathan Coates
b805a34c2d Merge branch 'mc-1.20.x' into mc-1.21.x 2025-03-16 16:28:53 +00:00
Jonathan Coates
b03546a158 Add game tests to check our blocks are inventories
See #2141
2025-03-16 15:59:43 +00:00
Jonathan Coates
582713467f Remove air blocks from test structures
This is a bit nasty, but makes the structure files *significtly* smaller
(1/4 the size), so feels worth doing.
2025-03-16 15:59:43 +00:00
Jonathan Coates
b6f41a0df5 Fix several issues with char/paste event validation
- Fix isValidClipboard always returning true.
 - Fix characters >=128 being rejected. We changed the signature from a
   byte to an int in 0f123b5efd, but
   didn't update all call sites.

   Valhalla cannot come soon enough. I would love to be able to have
   (cheap) wrapper classes for some of these types.

See Zeus-guy's comments in #860.
2025-03-16 14:07:15 +00:00
Jonathan Coates
594738a022 Standardised item details docs a little
Sort of closes #2125. I've really struggled to find a way to make it
clear that the information returned here is a snapshot of the current
item, and not a live view and/or proxy. Most wordings I've tried end up
feeling really clunky — given that this is a relatively rare
misunderstanding, let's not stress about this too much.
2025-03-16 10:25:57 +00:00
Jonathan Coates
27f2ab364c Clean up disk <-> drive right clicking
Oh dear. I'd originally set out to *remove* logic from DiskItem — we're
so close to being able to remove this item in 1.21! However, while
looking at this code, I realised I could remove the whole Forge-specific
doesSneakBypassUse.

We now remove the use hook on the block, and override useOn on the item.
Obvious in retrospect!
2025-03-15 12:28:29 +00:00
Jonathan Coates
5a43273757 docs: specify valid types for settings.define (#2140) 2025-03-13 07:10:30 +00:00
Drew Edwards
97e28516fb docs: specify valid types for settings.define 2025-03-13 01:40:08 +00:00
Jonathan Coates
676fb5fb53 Remove usages of onNeighborChange
Oh. This is from ye olde days (it's one of the first PRs to CC[^1]!). In
pre-1.13 days, furnaces changing their lit state would replace the block
(creating a new BE) and then set back the old BE. CC wouldn't pick up
the second event, and so would continue to use the peripheral from the
first.

We don't really need this any more, for a couple of reasons:
 a) Furnaces don't do this any more.

 b) Peripherals are now refreshed on the next tick rather than
    immediately.

 c) Forge's capabilities have an explicit invalidate() hook already. This
    technically only detects *removing* block entities, but I'm not sure
    there's any cases where you add a block entity without also
    triggering a block state change.

Ironically, the place we probably need this more is Fabric, where the
lookup API doesn't have a public invalidate hook (it's hidden away in
the BlockApiCache). I'm mostly relying on c) here, in that we just won't
see this happen in practice.

[^1]: https://github.com/dan200/ComputerCraft/pull/180
2025-03-09 15:22:49 +00:00
Jonathan Coates
08dc08b5a3 Replace appendHoverText with component-based tooltips
We have several items (e.g. ComputerItem), which only exist for their
custom tooltip implementation. We remove these, and replace them
vanilla-style component-based tooltips (TooltipProvider).

The implementation here is a little janky — as the vanilla list of
components is hard-coded, and neither mod loader offers a way to extend
it. For now we just use the generic mod-loader tooltip hook — this
probably would be easier with a mixin, but let's do things Properly.

It would be nice to fully remove DiskItem (we only keep this around for
doesSneakBypassUse), but that can be a future task.
2025-03-09 13:39:45 +00:00
Jonathan Coates
8f4d4038f6 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-03-09 12:35:44 +00:00
Jonathan Coates
63ba3fe274 Fix printout crafting
Introduced by the previous commit — I'd made one of the checks too lax.
Add some tests for this, so it doesn't happen again, though this code
does get a complete rewrite in 1.21 anyway >_>.
2025-03-09 12:19:59 +00:00
Jonathan Coates
749b3df227 Remove PrintoutItem.getType
Kinda surprised this is still around! Not sure why I kept it post
the-flattening really, it's been redundant for a while.
2025-03-09 11:46:36 +00:00
Jonathan Coates
b97634b717 Flesh out LuaTable a bit
Add a whole buncha helper methods for parsing values, much like
IArguments. This allows us to remove TableHelper. Gosh, that dates back
to 2018!
2025-03-08 23:39:11 +00:00
Jonathan Coates
8ade1c38ac Send less computer BE data to the client
We only sent the id, label and lock code to the client for pick-block
interactions. Now that's handled on the server, we don't need this any
more!
2025-03-05 20:46:06 +00:00
Jonathan Coates
1b8344d0a3 Ignore shader loading errors
Another go at fixing #2127.

In a892739f8e we set the precision on the
Tbo uniform. However, this is stripped in the shader pre-processing
Pojav/gl4es does, and so has no effect. As a (terrible) workaround, we
now just ignore shader loading errors. This probably does leak memory
(we'll never clean up the program), but there's not much we can do about
that.
2025-03-05 19:01:32 +00:00
Jonathan Coates
b42bc0a01a Bump Loom and vanilla-extract versions 2025-03-05 18:49:03 +00:00
Jonathan Coates
70a7478529 Ignore some components when sending item to client
We send the item-form of the current computer in the computer menu data.
However, this leaks the current LockCode, as we include all components.
We now only gather a safe subset of components when constructing the
item.
2025-03-05 18:20:05 +00:00
Jonathan Coates
0cff73e2fc Add turtle.getEquipped{Left,Right}
These just return details about the currently equipped *item*. This
allows us to expose information about the currently equipped upgrade,
without having to invent a whole new format.

Docs are a bit consise, but didn't really know how to flesh them out any
further.

Fixes #964, fixes #1613, closes #1692.
2025-03-03 21:30:19 +00:00
Jonathan Coates
05163a4911 Store command computers in a separate folder
- Remove the /computercraft-computer-folder client command, and replace
   it with OPEN_FILE on NeoForge, or a /computercraft-open-folder
   command on Fabric (which now accepts a path, rather than an id).

 - Store command computer files in "computercraft/command_computer/<id>".

Fixes #1581.
2025-03-03 20:54:06 +00:00
Jonathan Coates
a892739f8e Specify precision in monitor fragment shader
Some people run Minecraft on OpenGL ES GPUs via the gl4es translation
bridge. This sets the default precision for floats and ints, but not
usamplerBuffer.

Using lowp should be fine here (we don't need to encode much info!), but
we use mediump just in case. Have run this through the Mali Offline
compiler, and it seems fine with it.

Fixes #2127.
2025-03-03 10:13:26 +00:00
Jonathan Coates
9277aa33e9 Update to 1.21.4
Please don't talk to me about this. The first couple of hours of this
update were quite enjoyable, and then the rest was one of the most
miserable times I've had modding.

This has been a real slog, partly due to some large MC changes (item
models are a great change, but a pain to adapt to), and partly due to
mental health reasons — honestly, I've opened up my IDE so many times,
and then just closed it because I've hated the thought of even working
on this.

I will publish this to my maven, so mod authors can depend on it, but I
have no plans to publish a 1.21.4 version. 1.21.5 is right around the
corner (again, with some cool, but no-doubt painful changes), and I
need some time to focus on some breaking changes.

This commit actually includes the 1.21.3 update — the git history got so
messy here, so I just clobbered the whole thing. Sorry.

== Rendering ==

 - Remove TBO monitor renderer: There was a big overhaul to how shaders
   are defined and loaded in 1.21.2. It might have been possible to
   update the monitor shader code to this version, it doesn't see much
   use nowadays, so let's just delete it.

   This is a real shame — the TBO renderer was one of my favourite
   projects I've worked on. Unfortunately, it just doesn't seem worth
   the ongoing maintenance burden. It lives on in the standalone
   emulator :D.

 - Similarly, the VBO rendering code got a bit of an overhaul. We no
   longer use a custom VBO subclass, and instead just hack vanilla's to
   support changing the number of vertices rendered.

   This does mean we need to construct a MeshData, rather than a raw
   ByteBuffer. This isn't too hard, but not sure how it'll play with
   Iris. Given recent vanilla performance improvements, maybe we can
   remove our Unsafe code and use a normal BufferBuilder now.

 - Remove our custom emissive model code, now that vanilla supports
   it. We should add emissive textures to some other models at some
   point.

 - Remove mod-loader specific model code, and replace it with vanilla's
   ItemModel. This does constrain the design of turtle upgrade modellers
   quite a bit — we now only accept an untransformed BakedModel or a
   transformed ItemStack model. We may relax this in the future,
   unclear.

   This change does mean that updsidedown turtles are broken. RIP :(.

 - Entity rendering now separates reading state from the entity from
   actual rendering. This means we need to pass some extra state around
   for item frames. Easy on Forge, but requires a mixin on Fabric.

== Recipes ==

There were several major changes to ingredients this update. The code
here hasn't been very well tested right now — might be nice to add some
game tests for this.

 - Ingredients can no longer be constructed directly from a tag key (it
   needs to be fetched from the current registries), so the recipe
   generation code needs a bit of a reshuffle.

 - DiskRecipe now accepts a custom list of ingredients, rather than
   being hard-coded (fixes #1755). Recipes can now return custom
   `RecipeDisplay`s used to show a recipe in the crafting book. We use
   this to replace the impostor recipes.

   I'm not entirely sure how well this'll play with other recipe
   mods. Here's hoping.

 - Similarly, our recipe mod integration has been updated to use
   RecipeDisplay. We had to do this as ingredients no longer accept
   arbitrary ItemStacks (only a specific item).

== Misc ==

 - Blocks/items now need to know their ID ahead of time (so they can
   compute their description). This requires some reshuffling to the
   registration code, but it's pretty minor.

 - updateShape and neighborChanged no longer take a direction (the
   Orientation is mostly null) and so invalidates all redstone and
   peripherals.

 - All the positions were lowered by one in game tests. It's a good
   change (they now match the positions in structures), but annoying to
   update for!
2025-03-02 21:34:47 +00:00
Jonathan Coates
f8785a092f Fix particle texture for turtle colour model
This is never actually used in practice, but let's avoid any missing
texture reference warnings! Embarrassing that I hadn't noticed this
before!
2025-03-02 21:14:27 +00:00
Jonathan Coates
598fc4aefd Merge branch 'mc-1.20.x' into mc-1.21.x 2025-03-01 22:49:56 +00:00
Jonathan Coates
dd7e8fcefc Bump CC:T to 1.115.1 2025-03-01 22:35:29 +00:00
Jonathan Coates
29c8f96912 Sync translations from CrowdIn
I remembered to do this *before* the update! 🎉
2025-03-01 22:34:45 +00:00
Jonathan Coates
b9267ecbfc Resize lectern pocket computer textures
This bumps them to be 48x48, which allows them to be downscaled to a
mipmap level of 4. We possibly should bump these to be 64x64 (actual
power of two), but I kinda want to avoid that, as it's so much wasted
space. If this does become a problem, we should probably put these on a
separate atlas instead.
2025-03-01 22:24:27 +00:00
Jonathan Coates
9d2c2db22b Fix speaker.playAudio not updating volume
Honestly, the whole design around volume and playSound/playAudio is a
little janky — it probably should be a separate setVolume method which
updates directly. But too late to change that now, so let's do what we
can.

See #2108
2025-03-01 20:28:37 +00:00
ellellie
6660966320 Update Create dependency to 6.0.0 (#2117) 2025-03-01 19:56:43 +00:00
Jonathan Coates
3acb231f01 Write some docs for migrating from MC 1.20 -> 1.21
Almost definitely too late (and not sure if anyone will read them
anyway), but we can but try.
2025-02-21 20:12:44 +00:00
Jonathan Coates
16324e1eac Flesh out detail provider/registry docs 2025-02-19 21:08:56 +00:00
Jonathan Coates
fa33949113 Merge AbstractComputerItem and ComputerItem
ComputerItem was just an empty subclass, so no sense keeping around. We
probably could get rid of CommandComputerItem too (just do an `instanceof
GameMasterBlock` in `getPlacementState`), but there's no rush.
2025-02-16 21:12:35 +00:00
Jonathan Coates
0c04d9de47 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-02-16 21:04:28 +00:00
Jonathan Coates
32f5c38485 Remove IMedia implementations from our items
We now register these separately, rather than relying on the implicit
IMedia. This allows us to share a bit more logic between
PocketComputerItem and AbstractComputerItem. This doesn't make much
difference on 1.20.1, but does help a bit more on 1.21.1.
2025-02-16 20:32:01 +00:00
Jonathan Coates
01fe949b3e Use Fabric's Item Lookup for registering media providers
We'll switch to capabilities on the (Neo)Forge side for 1.21, when the
capability system is less painful, and then fully deprecate in 1.21.4.
2025-02-16 20:32:01 +00:00
Jonathan Coates
c03fce275e Add some abstractions for registering capabilities
This is pretty useless right now (not even going to try to use it for
Forge), but should be a bit more useful for 1.21.
2025-02-16 20:32:00 +00:00
Jonathan Coates
0998acaa82 Switch to JSpecify annotations
Now, hear me out, what if instead of having three @Nullable annotations,
we had *four*?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

See discussion in #860 for more context.
2025-02-12 18:45:40 +00:00
Jonathan Coates
1278246cf7 Add back MoreRed support
I removed this in fc834cd97f, way back in
late 2024. Looks like it's been updating in the meantime and I hadn't
noticed, so add it back.

I've simplified the code a little bit, to make use of our NeoForge's new
capability system, but otherwise it's almost exactly the same :D.
2025-02-12 13:40:58 +00:00
Jonathan Coates
88cb03be6b Clean up the parallel API
- Store the filter alongside the coroutine rather than in a separate
   table (like we do in multishell).

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

 - Rename some variables to be a bit more consistent. This makes this
   commit look noisier than it is. Sorry!
2025-02-09 16:53:59 +00:00
Jonathan Coates
1e25fa9bc3 Bump CC:T to 1.114.5 2025-02-09 10:16:45 +00:00
Jonathan Coates
74f707aaea Create a new CraftingInput for each craft
It's not actually safe to reuse this, as we need to recompute the
internal StackedContents each time the inventory changes, otherwise
ShapelessRecipe.matches will continue to return true, even if the actual
inventory doesn't include the required items.

Fixes #2094
2025-02-07 09:45:14 +00:00
Jonathan Coates
9bb62b047a Merge branch 'mc-1.20.x' into mc-1.21.x 2025-01-31 21:41:15 +00:00
Jonathan Coates
4360485880 Bump CC:T to 1.114.4 2025-01-31 21:09:57 +00:00
Jonathan Coates
b69a44a927 Redo turtle move checks
Oh, this is so broken, and really has been since the 1.13 update, if not
earlier.

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

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

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

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

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

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

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

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

[^1]: 7a7993deb8/src/main/java/com/simibubi/create/foundation/utility/NBTProcessors.java (L179)
2025-01-21 20:41:55 +00:00
Jonathan Coates
4f3663ccc9 Allow overriding computer/floppy capacity
This adds a new "computercraft:storage_capacity" component to items (and
"Capacity" NBT tag to BEs), that overrides the capacity for the given
item.

Fixes #1814
2025-01-21 10:04:46 +00:00
Jonathan Coates
53425c1e76 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-01-20 22:22:09 +00:00
Jonathan Coates
55edced9de Move GUI sprites to the sprites/ folder
This is where vanilla will read the sprites from in future versions, so
means we have a consistent layout between versions.

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

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

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

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

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

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

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

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

We now add a new class (ComputerEvents), which has a series of *static*
methods, that can queue an event on a ComputerEvents.Receiver object.
This is a bit of an odd indirection (why not just make them instance
methods on Receiver?!), but I don't really want those methods leaking
everywhere.
2025-01-18 19:26:10 +00:00
Jonathan Coates
6739c4c6c0 Wait for computers to run each tick in gametests 2025-01-17 18:43:19 +00:00
Jonathan Coates
d6749f8461 Set issue type in the templates
We already have the label, so it's not quite clear if it's worth it, but
let make our issue board look even more like a tube of smarties.
2025-01-17 17:25:39 +00:00
Jonathan Coates
d697c47b80 Catch ModdedConfig.getFilePath errors
This occurs when syncing the server config to the client. Ideally we'd
not hit this code path in the first place, but unfortunately there's no
way to tell where the config file comes from.

Fixes #2065
2025-01-17 17:14:06 +00:00
Jonathan Coates
5ba7f99326 Add back inputs on processResources
I kinda thought that Gradle would be smart enough to know that these
were input (given they're passed to expand), but apparently not :/.
2025-01-14 21:26:31 +00:00
Jonathan Coates
4710ee5bcc Merge branch 'mc-1.20.x' into mc-1.21.x
As part of this, we also rewrite some of the turtle placing code, and
how it uses the turtle_can_use tag:

Minecraft 1.21 cleaned up the item/block clicking code a little bit,
splitting Block.use into Block.useItemOn and Block.useWithoutItem. The
first of these is pretty much exactly what we wanted in the first place,
so the tag was kinda redundant and we commented it out in the 1.21
update.

This was never meant to be a long-term fix, but time has gone by anyway.
We now check that tag, and call useWithoutItem() if present —
effectively restoring the previous behaviour.

Fixes #2011
2025-01-14 21:26:11 +00:00
Jonathan Coates
62c9e5b08f Add back missing translations
Lost in d3a3ab3c21, due to changes in
ea670cc358.
2025-01-14 18:14:51 +00:00
Jonathan Coates
2ca5850060 Bump CC:T to 1.114.3 2025-01-14 10:24:23 +00:00
Jonathan Coates
ed631b05e7 Fix TurtleTool using the wrong stack
When "placing" the item (e.g. hoeing soil), we were using the tool item,
rather than the passed stack. This was introduced in
9a914e75c4, so never made it into a
release.
2025-01-14 10:24:23 +00:00
Jonathan Coates
a2b9490d5c Some printer gametests
These are a little ugly, but do catch some issues we've seen in the
past. Fixes #1682 (for now), and on its birthday too!
2025-01-14 09:38:22 +00:00
Jonathan Coates
8204944b5f More Gradle cleanup
Mostly configuration cache support. And an aborted attempt at updating
spotless, which just resulted in a bunch of ktlint issues :/.
2025-01-14 08:48:48 +00:00
Jonathan Coates
ef0af67e96 Share some dependency exclusion code
- Disable Gradle module metadata for all Minecraft projects
 - Run dependency exclusion code for all projects

We need the former for MDG on 1.21, so might as well do some other
cleanup while we're here.
2025-01-13 00:02:54 +00:00
Jonathan Coates
9a914e75c4 Rethink PlatformHelper.useOn
useOn is now only responsible for firing the actual mod loader events,
and just returns the result of firing that event. The actual calling of
Block.use/Item.useOn now live in TurtlePlaceCommand.

This isn't especially useful for 1.20.1, but is more relevant on 1.21.1
when we look at #2011, as the shared code is much larger.
2025-01-12 22:01:43 +00:00
Jonathan Coates
4a532952d4 Merge branch 'mc-1.20.x' into mc-1.21.x 2025-01-12 20:48:49 +00:00
Jonathan Coates
546577041b More turtle game tests
See #1682. Mostly trying to capture some behaviour I know has changed in
1.21, so we can verify it more easily.
2025-01-12 20:29:37 +00:00
Jonathan Coates
f881c0ced0 A few more gametests, update to Gradle 8.12
Okay, listen. I started writing a few more gametests (see #1682), and
then thought I'd do a cheeky Gradle update. However, that broke
vanilla-extract[^1], and also triggered a load of deprecation warnings,
and at that point it was too late to separate the too.

[^1]: 8975ed5a7b
2025-01-12 18:26:51 +00:00
Jonathan Coates
0b389e04b0 Move Fabric datagen mixins to datagen module
This way they don't interfere with other mods doing datagen.

Also bump reuse version, to fix issues with globs.
2025-01-12 12:44:57 +00:00
Jonathan Coates
d3a3ab3c21 Sync translations from Crowdin
Closes #2044

Co-authored-by: Yaroslav Petryk <Yaroslav01@ukr.net>
2025-01-12 11:47:58 +00:00
Jonathan Coates
22e6c06e59 Don't store terminal snapshot as a ByteBuf
There's too many issues here around object pooling and reference
counting, that it's just not practical to correctly handle. We now just
use a plain old byte[] rather than a ByteBuf.

This does mean the array now lives on the Java heap rather than the
direct heap, but I *think* that's fine. It's something that is hard to
measure.

Fixes #2059
2025-01-12 10:37:27 +00:00
Jonathan Coates
7337b91692 Merge branch 'mc-1.20.x' into mc-1.21.x
Oh, I'm sure I missed something here. This was a nasty merge, has the
docs have changed so much in each version.
2025-01-11 17:54:15 +00:00
Jonathan Coates
3c46b8acd7 Clean up Javadocs a little
I've no motivation for modding right now, but always got time for build
system busywork!

CC:T (and CC before that) has always published its API docs. However,
they're not always the most helpful — they're useful if you know what
you're looking for, but aren't a good getting-started guide.

Part of the issue here is there's no examples, and everything is
described pretty abstractly. I have occasionally tried to improve this
(e.g. the peripheral docs in bdffabc08e),
but it's a long road.

This commit adds a new example mod, which registers peripherals, an API
and a turtle upgrade. While the mod itself isn't exported as part of the
docs, we reference blocks of it using Java's new {@snippet} tag.

 - Switch the Forge project to use NeoForge's new Legacy MDG plugin. We
   don't *need* to do this, but it means the build logic for Forge and
   NeoForge is more closely aligned.

 - Add a new SnippetTaglet, which is a partial backport of Java 18+'s
   {@snippet}.

 - Add an example mod. This is a working multi-loader mod, complete with
   datagen (albeit with no good multi-loader abstractions).

 - Move our existing <pre>{@code ...}</pre> blocks into the example mod,
   replacing them with {@snippet}s.

 - Add a new overview page to the docs, providing some getting-started
   information. We had this already in the dan200.computercraft.api
   package docs, but it's not especially visible there.
2025-01-09 20:47:51 +00:00
Jonathan Coates
d9fc1c3a80 Remove mention of submitting translations via PRs
Turns out it's possible, but a bit of a faff with CrowdIn. Why is all
translation software terrible!
2025-01-05 23:25:29 +00:00
Jonathan Coates
479aabdd09 Tweaks to CONTRIBUTING.md 2025-01-05 23:23:24 +00:00
Jonathan Coates
ad74893058 Remove redundant condition in io.read 2025-01-05 23:02:16 +00:00
Jonathan Coates
2ba6d5815b Fix fs.isDriveRoot for missing files
fs.getDrive returns nil for missing files, rather than the mount of the
parent file. This is a bit confusing — other mount-related functions
(e.g. getFreeSpace) find the nearest mount — but I think it's too late
to change this. Instead, we check if the file exists first.
2024-12-31 11:12:58 +00:00
tizu
7e2f490626 Show HTTP error in wget (#2037) 2024-12-22 16:45:04 +00:00
Jonathan Coates
4dc649d5e5 Merge pull request #2029 from MCJack123/patch-19
Add `turtle.getItemDetail` `detailed` flag introduction date
2024-12-12 09:18:44 +00:00
JackMacWindows
5bab415790 Add turtle.getItemDetail detailed flag introduction date 2024-12-12 00:44:25 -05:00
Jonathan Coates
f04c699df6 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-12-05 12:19:16 +00:00
Jonathan Coates
9bbf3f3e1d Move datagen to its own source set
MC 1.21.4 means we have to move more data generation code into the
client source set. Given all this code movement, it probably makes sense
to put data generation in a separate source set instead.

1.21.4 also has split data generators for client and server, but neither
mod loader recommends this. This means we can/should merge DataProviders
and ClientDataProviders into a single class.

Data generators are no longer bundled with the jar, which does reduce
file size, but by a tiny amount (~70KiB).
2024-12-05 12:03:45 +00:00
Jonathan Coates
a3f8e653d4 Fix redstone relay not being registered as a peripheral
This was only present on the 1.21 NF version, hence not being noticed
before. Fixes #2020.

I pruned my Gradle cache recently, and I'm on some truly terrible hotel
wifi, so this is entirely untested. No beta, we die like men.
2024-12-03 07:31:25 +00:00
Jonathan Coates
7c02979c22 Upload jars in CI earlier
This means they're uploaded even if the linters or tests fail.
2024-11-30 12:07:55 +00:00
Jonathan Coates
fdb65c9368 Link to monitor events from the main monitor page 2024-11-28 10:31:36 +00:00
Jonathan Coates
ea670cc358 Try to unify our config files a bit
I've tried so many rewrites of the config system over the last few
months, in an attempt to get started on #1727. All of them stink, so
this is an attempt to apply some of the cleanup.

 - Move some of the common logic into ConfigFile. This means we now
   store more information ourselves for Forge, rather than reading it
   out of the ForgeConfigSpec.

 - Don't include the Range/Allowed keys in the translation key. This was
   mostly there because of how we read comments from Forge, but it never
   made much sense.

 - Remove our separate Trie structure, and just encode the tree as part
   of the children of a Group.
2024-11-24 21:53:04 +00:00
Jonathan Coates
b7396f3796 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-11-23 09:36:50 +00:00
Jonathan Coates
1963e0160f Bump CC:T to 1.114.2
Ahhhh, I hate graphics code.
2024-11-23 09:34:03 +00:00
Jonathan Coates
9a06904634 Only skip cursor if it exists
If the cursor is not visible then we'd end up blinking the last
character on the screen. And if the screen was empty we'd spew the logs
with GL errors.
2024-11-23 09:31:04 +00:00
Jonathan Coates
5eb50ecb06 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-11-23 09:16:48 +00:00
Jonathan Coates
5e24ad17d7 Bump CC:T to 1.114.1 2024-11-23 09:14:53 +00:00
Jonathan Coates
8b1cb09ddf Update translations 2024-11-23 09:10:50 +00:00
Jonathan Coates
7af2c14327 Move block entity component fixes to separate fixer
Some mods run their own datafixer chain, rather than piggybacking on top
of vanilla's. This is A BAD IDEA, but what can you do. If such a mod
tries to use ItemStackComponentizationFix in their own schema, then
CC:T's mixins will try to look up the turtle block entitie, and fail (as
they're not registered under the modded schema).

We now inject the block entity fix as a separate fixer, rather than
abusing ItemStackComponentizationFix.

See #2012
2024-11-23 08:53:54 +00:00
Jonathan Coates
d1a6b043c2 Clean up monitor cursor rendering
- Use the correct index count for the cursor quad. Monitors are now
   rendered as quads, rather than triangles.
 - *Skip* rendering the cursor vertex, rather than additionally
   rendering it.

I confess, I'm baffled how this code was ever written. From what I can
tell, this has been broken since it was first introduced in
4228011b84, and I'm sure I tested it then.

Fixes #2013. Probably.
2024-11-23 08:24:11 +00:00
Jonathan Coates
cddb8fec11 Fix crash when lectern has no item
This should never happen in practice, but might happen when using
/setblock or after world corruption, so let's be careful here.

Closes #2014
2024-11-19 21:35:00 +00:00
Jonathan Coates
1d7d8006d4 Stop publishing to CurseForge
Been dragging my feet over this for a while now, but increasingly
uncomfortable with Overwolf. I'm not going to delete the project (or any
existing versions), just not publish any new versions there.
2024-11-16 15:19:00 +00:00
Jonathan Coates
63bdc2537c Fix monitor events using the wrong computer set
Fixes #2010
2024-11-15 18:43:28 +00:00
Jonathan Coates
f776b17150 Fix argument order of math.atan in changelog 2024-11-15 15:32:40 +00:00
Jonathan Coates
0056709999 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-11-15 09:25:46 +00:00
Jonathan Coates
31da2555cb Bump CC:T to 1.114.0 2024-11-15 08:22:05 +00:00
Jonathan Coates
9b19a93ab9 Sync translations from Crowdin
Ended up writing my own janky script to do this, rather than use the GH
integration. I'm not proud of this, but it works.
2024-11-15 08:14:06 +00:00
Jonathan Coates
0c8e757314 Several cleanup to turtle crafting upgrade
- Don't construct a fake player when crafting: vanilla now has its own
   automated crafting, so no longer requires the presence of a player.

 - Fix remainder stack not being set in some situations. Closes #2007.
2024-11-15 07:31:49 +00:00
Jonathan Coates
f39e86bb10 Bump NeoGradle version
This uses a shared asset directory (previously it was per-version), so
much more disk friendly.
2024-11-14 11:01:23 +00:00
Jonathan Coates
ad52117f0f Syntax highlight more docstrings
This is going to conflict horribly with MC 1.21 (where we started using
@snippet), but hopefully this still helps.
2024-11-13 11:00:46 +00:00
Jonathan Coates
bdffabc08e Clarify docs around registering peripherals 2024-11-13 10:19:10 +00:00
Jonathan Coates
87ce41f251 Update Cobalt to 0.9.5
- Fix several issues with large doubles
 - Fix metatable cache not being cleared
2024-11-12 21:11:22 +00:00
Jonathan Coates
e7c7919cad Add test for turtle crafting with remainders
See #2007. This isn't an issue on 1.20.1, so it doesn't fix the issue,
but good to have the test everywhere.
2024-11-12 09:17:03 +00:00
Jonathan Coates
4f66ac79d3 Add redstone relay block (#2002)
- Move redstone methods out of the IAPIEnvironment, and into a new
   RedstoneAccess. We similarly move the implementation from Environment
   into a new RedstoneState class.

   The interface is possibly a little redundant (interfaces with a
   single implementation are always a little suspect), but it's nice to
   keep the consumer/producer interfaces separate.

 - Abstract most redstone API methods into a separate shared class, that
   can be used by both the rs API and the new redstone relay.

 - Add the new redstone relay block.

The docs are probably a little lacking here, but I really struggled to
write anything which wasn't just "look, it's the same as the redstone
API".
2024-11-12 09:05:27 +00:00
Jonathan Coates
ba6da3bc6c Update item image exporter to work on 1.20.x 2024-11-12 08:11:09 +00:00
Jonathan Coates
b742745854 Cancel no-longer-needed timers
Several functions accept a "timeout" argument, which is implemented by
starting a timer, and then racing the desired output against the timer
event.

However, if the timer never wins, we weren't cancelling the timer, and
so it was still queued. This is especially problematic if dozens or
hundreds of rednet (or websocket) messages are received in quick
succession, as we could fill the entire event queue, and stall the
computer.

See #1995
2024-11-10 21:08:05 +00:00
Jonathan Coates
3293639adf Normalise language files 2024-11-05 10:00:08 +00:00
Jonathan Coates
064ff31830 Don't reset client pocket state when changing level
As part of the multi-loader work, we unified some of our event listening
code (0908acbe9b). This incorrectly caused
client pocket computer state to be reset when the player changes
dimension, rather than when the player (dis)connects.

The server code isn't aware of this behaviour, and so does not resend
pocket computer state when the player moves level. We could change this,
but just fixing when we clear the pocket computer state is a much nicer
fix!

Fixes #2004
2024-10-31 09:33:22 +00:00
Jonathan Coates
5d473725d5 Don't word-wrap in the bug report template
Oh, I wish GHFM handled soft-breaks as soft-breaks, rather than turning
them into hard ones :/.
2024-10-31 08:32:44 +00:00
Jonathan Coates
97a2f2dbdd A desperate plea to get people to include logs 2024-10-31 08:31:35 +00:00
Jonathan Coates
37c4789fa4 Fix Czech language mapping 2024-10-31 08:31:21 +00:00
Jonathan Coates
0aaeeeee24 Don't log HTTP errors
We don't do this for websockets, so maybe we can get away without this
for HTTP ones too? Closes #1975.
2024-10-27 16:07:17 +00:00
Jonathan Coates
2155ec3d63 Give up on codecov
v4 of the action seems to be much more restrictive in what can upload
coveage. I'll miss this (the historic view of coverage was nice!), but
not worth trying to get working again.

We also stop running client tests. These break so often, we only really
ran them for code coverage reasons.
2024-10-27 11:01:42 +00:00
NotSquidDev
0da906fc93 New Crowdin updates (#1954) 2024-10-27 10:18:26 +00:00
Jonathan Coates
9e5e6a1b60 Update npm packages and illuaminate 2024-10-27 10:01:29 +00:00
Jonathan Coates
dcc74e15c7 Fix pocket upgrades not applying after crafting
The most annoying thing about pocket computers is handling computer
state (label, upgrades, etc...). Unlike other computers, which are tied
to a specific block entity, pocket computers float untethered. We can't
hold a reference to a specific item stack (as the computer might be
moved between inventories, crafted, etc...), so instead we explicitly
sync data between the computer and *current* stack, whenever the holding
player/entity is ticked.

In ed0b156e05 I rewrote this syncing code
to always treat the computer as the source of truth. Upgrades would be
copied to the computer, but never the other way round. However, this
meant that upgrades obtained by crafting would never be detected,
requiring the computer to be destroyed and recreated.

A more long-term fix here is probably to rewrite IPocketAccess to only
allow updating upgrade data on the main thread, and when we have a valid
PocketHolder. This is a breaking API change though, and so will have to
wait for 1.21.3.

For now, we just add a hook that refreshes the upgrade after crafting.

Fixes #1957
2024-10-27 09:49:01 +00:00
Jonathan Coates
63181e73a1 Unify Iris integrations
Iris now has built-in support for NeoForge, so we can use the same
integration on both.

We also re-enable Forge's client tests, and test Iris there too.

Fixes #1967
2024-09-11 20:10:38 +01:00
Jonathan Coates
4f3247a0e2 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-09-11 19:28:13 +01:00
Jonathan Coates
4f15f4197b Fix turtle owning player not being set
Fixes #1948, fixes #1949
2024-08-21 18:01:58 +01:00
Jonathan Coates
0d8ac304c7 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-08-19 20:54:14 +01:00
Jonathan Coates
fdd5f49369 Update JEI to fix crash with NF 2024-08-19 18:28:59 +01:00
Jonathan Coates
8bd4c3370e Update to Minecraft 1.21.1
I'm not sure we *need* to do this (the two versions are compatible), but
probably a good idea anyway.
2024-08-14 18:38:07 +01:00
Jonathan Coates
3eb84ffedd Fix several data fixer issues
Disk IDs and treasure disk colour were not being correctly converted.
This also adds several tests to ensure that these items are handled
correctly.

Closes #1934.
2024-08-14 09:24:07 +01:00
Jonathan Coates
dad6874638 Add detail provider for data components
Historically we'd provide specific data based on the current item and
NBT (hence BasicItemDetailProvider). However, with components, it now
makes sense to provide details for all items with a specific component.

This commit adds a new "ComponentDetailProvider" class, that reads a
component from an item stack (or other component holder) and provides
details about it if present.
2024-08-11 11:53:16 +01:00
Jonathan Coates
45cb597ecc Merge branch 'mc-1.20.x' into mc-1.21.x 2024-07-31 07:34:49 +01:00
Jonathan Coates
16577783d3 Fix incorrect offset in turtle crafting
This means we end up looking at the wrong slots, and thus fail to remove
items! Fixes #1918.
2024-07-31 06:40:37 +01:00
Jonathan Coates
c179da28f0 Make turtle colour opaque in BER
Fixes #1893
2024-07-31 06:30:43 +01:00
Jonathan Coates
2765abf971 Udpate to latest Neo, Fabric and Parchment
- Update to latest NeoForge, fixing issues with config API changes.
   Closes #1903.
 - Update to latest Fabric, switching to the ender pearl conventional
   tag, and new loot API.
2024-07-28 16:47:41 +01:00
Jonathan Coates
2c740bb904 Bump CC:T to 1.111.1 2024-07-04 19:08:06 +01:00
Jonathan Coates
0e4710a956 Update NF and NG
- Rename ToolActions to ItemAbilities. Closes #1881.
 - Remove our source set helper, as NG has built-in support for this
   now.
 - Remove our code to generate new JavaExec tasks from runs, as NG now
   generates JavaExec tasks normally.
2024-06-29 12:58:03 +01:00
Jonathan Coates
aca1d43550 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-06-29 10:50:44 +01:00
Jonathan Coates
f10e401aea Load turtle overlays from a registry
- Add a new computercraft:turtle_overlay dynamic registry, which stores
   turtle overlays. Turtle overlays are just a model id and an
   (optional) boolean flag, which specifies whether this overlay is
   compatible with the elf/christmas model.

 - Change the computercraft:overlay component to accept a
   Holder<TurtleOverlay> (instead of just a model ID). This accepts both
   an overlay ID or an inline overlay object (e.g. you can do
   cc:turtle_normal[computercraft:overlay={model:"foo"}].

 - Update turtle model and BE rendering code to render both the overlay
   and elf (if compatible). Fixes #1663.

 - Ideally we'd automatically load all models listed in the overlay
   registry. However, resource loading happens separately to datapacks,
   so we can't link the two.

   Instead, we add a new assets/computercraft/extra_models.json file
   that lists any additional models that should be loaded and baked.

   This file includes all built-in overlay models, but external resource
   packs and/or mods can easily extend it.
2024-06-27 20:57:43 +01:00
Jonathan Coates
1a1623075f Fix turtle labels not rendering
The X scale factor should now be flipped. I'm not quite sure what in MC
has meant this should be changed, possibly the cameraOrientation matrix?

Fixes #1872
2024-06-26 18:06:48 +01:00
Jonathan Coates
54a95e07a4 Fix monitors not updating on NeoForge 2024-06-23 09:21:15 +01:00
Jonathan Coates
efd9a0f315 Fix JEI integration for MC 1.21
- Use the client side registry access (possible now that we've moved
   JEI to the client).

 - Generate unique ids for JEI recipes.
2024-06-22 22:48:37 +01:00
Jonathan Coates
28f75a0687 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-06-22 22:33:18 +01:00
Jonathan Coates
4b102f16b3 Update to Minecraft 1.21
API Changes:

 - Minecraft had updated ModelResourceLocation to no longer inherit from
   ResourceLocation.

   To allow referencing both already baked models
   (ModelResourceLocation) and loading new models (via ResourceLocation)
   in turtle model loadders, we add a new "ModelLocation" class, that
   acts as a union between the two.

   I'm not entirely convinced by the design here, so might end up
   changing again before a stable release.o

 - Merge IMedia.getAudioTitle and IMedia.getAudio into a single
   IMedia.getAudio method, which now returns a JukeboxSong rather than a
   SoundEvent.

Other update notes:
 - Minecraft had rewritten how buffers are managed again. This is a
   fairly minor change for us (vertex -> addVertex, normal -> setNormal,
   etc...), with the exception that you can no longer use
   MultiBufferSource.immediate with the tesselator.

   I've replaced this with GuiGraphics.bufferSource, which appears to be
   fine, but worth keeping an eye on in case there's any odd render
   state issues.

 - Crafting now uses a CraftingInput (a list of items) rather than a
   CraftingContainer, which allows us to simplify turtle crafting code.
2024-06-22 16:19:59 +01:00
Jonathan Coates
bb933d0100 Merge branch 'mc-1.20.x' into mc-1.20.y 2024-06-21 08:36:18 +01:00
Jonathan Coates
de078e3037 Merge branch 'mc-1.20.x' into mc-1.20.y 2024-05-28 18:46:19 +01:00
Jonathan Coates
eb584aa94d Translate remaining item tags
These shouldn't appear in recipes, but just to stop Fabric complaining
:).
2024-05-09 18:47:28 +01:00
Jonathan Coates
ad70e2ad90 Make printout recipes a little more flexible
Rather than having one single hard-coded recipe, we now have separate
recipes for printed pages and printed books. These recipes are defined
in terms of

 - A list of ingredients (like shapeless recipes).
 - A result item.
 - An ingredient defining the acceptable page items (so printed page(s),
   but not books). This cannot overlap with any of the main ingredients.
 - The minimum number of printouts required.

We then override the shapeless recipe crafting logic to allow for
multiple printouts to appear.

It feels like it'd be nice to generalise this to a way of defining
shapeless recipes with variable-count ingredients (for instance, the
disk recipe could also be defined this way), but I don't think it's
worth it right now.

This solves some of the issues in #1755. Disk recipes have not been
changed yet.
2024-05-09 18:47:22 +01:00
Jonathan Coates
2c0d8263d3 Update to MC 1.20.6
- Update EMI and REI integration, and fix some issues with the upgrade
   crafting hooks.
 - Just use smooth stone for recipes, not #c:stone. We're mirroring
   redstone's crafting recipes here.
 - Some cleanup to printouts.
 - Remote upgrade data generators - these can be replaced with the
   standard registry data generators.
 - Remove the API's PlatformHelper - we no longer have any
   platform-specific code in the API.
2024-05-07 22:59:53 +01:00
Jonathan Coates
94c864759d Ignore enchantments/attributes on the original item
Turtle tools were not equippable, as we considered the stack enchanted
due to the item's base attribute modifiers. We now only check the
component patch for enchantments/attribute modifiers.

This also removes the craftItem property of tools - this hasn't worked
since we added support for enchanted tools!

Fixes #1810
2024-04-29 21:07:04 +01:00
Jonathan Coates
2226df7224 Small cleanup after testing
- Use TinyRemapper to remap mixins on Fabric. Mixins in the common
   project weren't being remapped correctly.

 - Update to latest NeoForge
   - Switch to the new tick events.
   - Call refreshDimensions() in the fake player constructor.
2024-04-28 22:02:12 +01:00
Jonathan Coates
959bdaeb61 Use a single upgrade type for modems
Replace turtle_modem_{normal,advanced} with a single turtle_modem
upgrade (and likewise for pocket upgrades). This is slightly more
complex (we now need a custom codec), but I think is more idiomatic.
2024-04-28 20:38:30 +01:00
Jonathan Coates
06ac373e83 Use components for upgrade adjectives
This makes quick-and-dirty datapacks a little easier, as you can now use
a hard-coded string rather than adding a language key.
2024-04-28 20:07:15 +01:00
Jonathan Coates
0aca6a4dc9 Remove several unused test files 2024-04-28 19:54:39 +01:00
Jonathan Coates
bf203bb1f3 Rewrite upgrades to use dynamic registries
Ever since 1.17, turtle and pocket upgrades have been loaded from
datpacks, rather than being hard-coded in Java. However, upgrades have
always been stored in our own registry-like structure, rather than using
vanilla's registries.

This has become a bit of a problem with the introduction of components.
The upgrade components now hold the upgrade object (rather than just its
id), which means we need the upgrades to be available much earlier (e.g.
when reading recipes).

The easiest fix here is to store upgrades in proper registries instead.
This means that upgrades can no longer be reloaded (it requires a world
restart), but otherwise is much nicer:

 - UpgradeData now stores a Holder<T> rather than a T.

 - UpgradeSerialiser has been renamed to UpgradeType. This now just
   provides a Codec<T>, rather than JSON and network reading/writing
   functions.

 - Upgrade classes no longer implement getUpgradeID(), but instead have
   a getType() function, which returns the associated UpgradeType.

 - Upgrades are now stored in turtle_upgrade (or pocket_upgrade) rather
   than turtle_upgrades (or pocket_upgrades). This will break existing
   datapacks, sorry!
2024-04-28 19:47:19 +01:00
Jonathan Coates
cd9840d1c1 Add workaround for inventory method test failure 2024-04-26 22:06:29 +01:00
Jonathan Coates
b9a002586c Update turtle reach limits to 1.20.5
We can replace our mixins with vanilla's built-in attributes.
2024-04-26 21:44:19 +01:00
Jonathan Coates
a3b07909b0 Replace some recipes with a more dynamic system
This adds a new "recipe function" system, that allows transforming the
result of a recipe according to some datapack-defined function.

Currently, we only provide one function: computercraft:copy_components,
which copies components from one of the ingredients to the result. This
allows us to replace several of our existing recipes:

 - Turtle overlay recipes are now defined as a normal shapeless recipe
   that copies all (non-overlay) components from the input turtle.

 - Computer conversion recipes (e.g. computer -> turtle, normal ->
   advanced) copy all components from the input computer to the result.

This is more complex (and thus more code), but also a little more
flexible, which hopefully is useful for someone :).
2024-04-26 21:44:18 +01:00
Jonathan Coates
d7786ee4b9 Merge branch 'mc-1.20.x' into mc-1.20.y 2024-04-26 18:38:15 +01:00
Jonathan Coates
188806e8b0 Actually update NeoForge to 1.20.5
NF now loads mods from neoforge.mods.toml rather than mods.toml, so CC
wasn't actually being loaded. Tests all passed, because they didn't get
run in the first place!
2024-04-26 17:57:20 +01:00
Jonathan Coates
01407544c9 Update to 1.20.5 (#1793)
- Switch most network code to use StreamCodec
 - Turtle/pocket computer upgrades now use DataComponentPatch instead of
   raw NBT.
2024-04-25 20:32:48 +00:00
Jonathan Coates
bd2fd9d4c8 Merge branch 'mc-1.20.x' into mc-1.20.y 2024-04-25 18:23:04 +01:00
Jonathan Coates
5c457950d8 Merge branch 'mc-1.20.x' into mc-1.20.y 2024-04-24 20:30:59 +01:00
Jonathan Coates
75f3ecce18 Render printout pages further forward in the UI
Rather than rendering the background further back. This was causing some
of the pages to not be rendered. I'm not quite sure why this is -- there
shouldn't be any z-fighting -- but this does work as a fix!

Fixes #1777
2024-04-08 12:07:53 +01:00
Jonathan Coates
688fdc40a6 Don't render background in the off-hand pocket UI
Fixes #1778
2024-04-08 12:02:25 +01:00
Jonathan Coates
22bd5309ba Merge branch 'mc-1.20.x' into mc-1.20.y 2024-04-07 22:06:49 +01:00
Jonathan Coates
c50d56d9fa Remove canClickRunClientCommand
This was added in 4675583e1c to handle
Forge on longer supporting RUN_COMMAND for client-side commands.
However, the mixins are still present on NF/1.20.4, so we don't need
this!
2024-03-23 15:38:18 +00:00
Jonathan Coates
7b9a156abc Register our block entities with DFU
This ensures that data fixers will be applied to items in these
inventories.
2024-03-22 21:47:11 +00:00
Jonathan Coates
0a9e5c78f3 Remove some deprecated code
Some of this is technically an API break, but 1.20.4 is pretty unstable.

 - Remove WiredNetwork from the public API
 - Remove legacy computer selectors
2024-03-22 21:36:52 +00:00
Jonathan Coates
da5885ef35 Merge branch 'mc-1.20.x' into mc-1.20.y 2024-03-22 21:23:49 +00:00
Jonathan Coates
240528cce5 Update NF and NG
Trying to rebuild after this OOM killed all my windows. I hate NeoGradle
so much.
2024-02-18 19:02:23 +00:00
Jonathan Coates
83f1f86888 Merge branch 'mc-1.20.x' into mc-1.20.y 2024-02-18 18:45:20 +00:00
Jonathan Coates
9c202bd1c2 Fix incorrect cast for JEI 2024-02-05 18:52:20 +00:00
Jonathan Coates
fc834cd97f Update to 1.20.4 2024-01-31 20:55:14 +00:00
1549 changed files with 32003 additions and 34214 deletions

View File

@@ -18,11 +18,6 @@ ij_any_if_brace_force = if_multiline
ij_any_for_brace_force = if_multiline
ij_any_spaces_within_array_initializer_braces = true
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_method_parameters_wrap = off
ij_kotlin_call_parameters_wrap = off
[*.md]
trim_trailing_whitespace = false
@@ -31,3 +26,27 @@ indent_size = 2
[*.yml]
indent_size = 2
[*.{kt,kts}]
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ij_kotlin_continuation_indent_size = 4
ij_kotlin_spaces_around_equality_operators = true
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
# Prefer to handle these manually
ij_kotlin_method_parameters_wrap = off
ij_kotlin_call_parameters_wrap = off
ij_kotlin_extends_list_wrap = off
ktlint_code_style = intellij_idea
ktlint_standard_class-naming = disabled
ktlint_standard_class-signature = disabled
ktlint_standard_function-naming = disabled
ktlint_standard_no-wildcard-imports = disabled
# FIXME: These two are disable right now as they're over-eager in putting things
# on the same line. We should set max_line_length and handle this properly.
ktlint_standard_function-signature = disabled
ktlint_standard_function-expression-body = disabled

View File

@@ -1,15 +1,18 @@
name: Bug report
description: Report some misbehaviour in the mod
labels: [ bug ]
type: bug
body:
- type: dropdown
id: mc-version
attributes:
label: Minecraft Version
description: What version of Minecraft are you using?
description: |
What version of Minecraft are you using? If your version is not listed, please try to reproduce on one of the supported versions.
options:
- 1.20.1
- 1.21.x
- 1.21.1
- 1.21.7
validations:
required: true
- type: input
@@ -26,8 +29,7 @@ body:
label: Details
description: |
Description of the bug. Please include the following:
- Logs: These will be located in the `logs/` directory of your Minecraft
instance. Please upload them as a gist or directly into this editor.
- Detailed reproduction steps: sometimes I can spot a bug pretty easily,
but often it's much more obscure. The more information I have to help
reproduce it, the quicker it'll get fixed.
- Logs: These will be located in the `logs/` directory of your Minecraft instance. This is always useful, even if it doesn't include errors, so please upload this!
- Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed.
![A gif of burning text reading "Upload your logs!!!"](https://tweaked.cc/images/logs.gif)

View File

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

View File

@@ -14,7 +14,7 @@ jobs:
- name: 📥 Set up Java
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 25
distribution: 'temurin'
- name: 📥 Setup Gradle
@@ -30,6 +30,18 @@ jobs:
- name: ⚒️ Build
run: ./gradlew assemble || ./gradlew assemble
- name: 📦 Prepare Jars
run: |
# Find the main jar and append the git hash onto it.
mkdir -p jars
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
- name: 📤 Upload Jar
uses: actions/upload-artifact@v4
with:
name: CC-Tweaked
path: ./jars
- name: Cache pre-commit
uses: actions/cache@v4
with:
@@ -50,30 +62,10 @@ jobs:
- name: 🧪 Run integration tests
run: ./gradlew runGametest
- name: 🧪 Run client tests
run: ./gradlew runGametestClient # Not checkClient, as no point running rendering tests.
# These are a little flaky on GH actions: its useful to run them, but don't break the build.
continue-on-error: true
- name: 🧪 Parse test reports
run: ./tools/parse-reports.py
if: ${{ failure() }}
- name: 📦 Prepare Jars
run: |
# Find the main jar and append the git hash onto it.
mkdir -p jars
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
- name: 📤 Upload Jar
uses: actions/upload-artifact@v4
with:
name: CC-Tweaked
path: ./jars
- name: 📤 Upload coverage
uses: codecov/codecov-action@v4
build-core:
strategy:
fail-fast: false
@@ -95,7 +87,7 @@ jobs:
- name: 📥 Set up Java
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 25
distribution: 'temurin'
- name: 📥 Setup Gradle

View File

@@ -17,7 +17,7 @@ jobs:
- name: 📥 Set up Java
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 25
distribution: 'temurin'
- name: 📥 Setup Gradle

1
.gitignore vendored
View File

@@ -27,6 +27,7 @@
*.iml
.idea
.gradle
.kotlin
*.DS_Store
/.classpath

View File

@@ -6,7 +6,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -20,14 +20,14 @@ repos:
exclude: "tsconfig\\.json$"
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: 2.7.2
rev: 3.6.0
hooks:
- id: editorconfig-checker
args: ['-disable-indentation']
exclude: "^(.*\\.(bat)|LICENSE)$"
- repo: https://github.com/fsfe/reuse-tool
rev: v4.0.3
rev: v6.2.0
hooks:
- id: reuse
@@ -58,6 +58,7 @@ repos:
exclude: |
(?x)^(
projects/[a-z]+/src/generated|
projects/[a-z]+/src/examples/generatedResources|
projects/core/src/test/resources/test-rom/data/json-parsing/|
.*\.dfpwm
)

View File

@@ -22,14 +22,13 @@ If you have a bug, suggestion, or other feedback, the best thing to do is [file
use the issue templates - they provide a useful hint on what information to provide.
## Translations
Translations are managed through [CrowdIn], an online interface for managing language strings. Translations may either
be contributed there, or directly via a pull request.
Translations are managed through [CrowdIn], an online interface for managing language strings.
## Setting up a development environment
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
- Make sure you've got the following software installed:
- Java Development Kit 17 (JDK). This can be downloaded from [Adoptium].
- Java Development Kit 25 (JDK). This can be downloaded from [Adoptium].
- [Git](https://git-scm.com/).
- [NodeJS 20 or later][node].
@@ -49,9 +48,12 @@ If you want to run CC:T in a normal Minecraft instance, run `./gradlew assemble`
`projects/forge/build/libs` (for Forge) or `projects/fabric/build/libs` (for Fabric).
## Developing CC: Tweaked
Before making any major changes to CC: Tweaked, I'd recommend you have a read of the [the architecture
document][architecture] first. While it's not a comprehensive document, it gives a good hint of where you should start
looking to make your changes. As always, if you're not sure, [do ask the community][community]!
Before making any major changes to CC: Tweaked, I'd recommend starting opening an issue or starting a discussion on
GitHub first. It's often helpful to discuss features before spending time developing them!
Once you're ready to start programming, have a read of the [the architecture document][architecture] first. While it's
not a comprehensive document, it gives a good hint of where you should start looking to make your changes. As always, if
you're not sure, [do ask the community][community]!
### Testing
When making larger changes, it may be useful to write a test to make sure your code works as expected.
@@ -86,8 +88,8 @@ You'll first need to [set up a development environment as above](#setting-up-a-d
Once this is set up, you can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code,
writing the resulting HTML into `./projects/web/build/site`, which can then be opened in a browser. When iterating on
documentation, you can instead run `./gradlew docWebsite -t`, which will rebuild documentation every time you change a
file.
documentation, you can instead run `./gradlew :web:assemble -x :web:compileTeaVM -t`, which will rebuild documentation
every time you change a file.
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific
@@ -99,7 +101,7 @@ about how you can build on that until you've covered everything!
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
[community]: README.md#community "Get in touch with the community."
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
[Adoptium]: https://adoptium.net/temurin/releases?version=25 "Download OpenJDK 25"
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
[docs]: https://tweaked.cc/ "CC: Tweaked documentation"
[ldoc]: http://stevedonovan.github.io/ldoc/ "ldoc, a Lua documentation generator."

View File

@@ -11,14 +11,13 @@ SPDX-License-Identifier: MPL-2.0
</picture>
[![Current build status](https://github.com/cc-tweaked/CC-Tweaked/workflows/Build/badge.svg)](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status")
[![Download CC: Tweaked on CurseForge](https://img.shields.io/static/v1?label=Download&message=CC:%20Tweaked&color=E04E14&logoColor=E04E14&logo=CurseForge)][CurseForge]
[![Download CC: Tweaked on Modrinth](https://img.shields.io/static/v1?label=Download&color=00AF5C&logoColor=00AF5C&logo=Modrinth&message=CC:%20Tweaked)][Modrinth]
CC: Tweaked is a mod for Minecraft which adds programmable computers, turtles and more to the game. A fork of the
much-beloved [ComputerCraft], it continues its legacy with improved performance and stability, along with a wealth of
new features.
CC: Tweaked can be installed from [CurseForge] or [Modrinth]. It runs on both [Minecraft Forge] and [Fabric].
CC: Tweaked can be installed from [Modrinth]. It runs on both [Minecraft Forge] and [Fabric].
## Contributing
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you want to get started
@@ -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")
@@ -62,19 +60,6 @@ dependencies {
}
```
When using ForgeGradle, you may also need to add the following:
```groovy
minecraft {
runs {
configureEach {
property 'mixin.env.remapRefMap', 'true'
property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg"
}
}
}
```
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
subject to change at any point. If you depend on functionality outside the API (or need to mixin to CC:T), please file
an issue to let me know!
@@ -83,7 +68,6 @@ We bundle the API sources with the jar, so documentation should be easily viewab
the generated documentation [can be browsed online](https://tweaked.cc/javadoc/).
[computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub"
[curseforge]: https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked from CurseForge"
[modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth"
[Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[Fabric]: https://fabricmc.net/use/installer/ "Download Fabric."

View File

@@ -8,18 +8,23 @@ SPDX-PackageSupplier = "Jonathan Coates <git@squiddev.cc>"
SPDX-PackageDownloadLocation = "https://github.com/cc-tweaked/cc-tweaked"
[[annotations]]
# Generated/data files are CC0.
SPDX-FileCopyrightText = "The CC: Tweaked Developers"
SPDX-License-Identifier = "CC0-1.0"
path = [
# Generated/data files are CC0.
"gradle/gradle-daemon-jvm.properties",
"projects/common/src/main/resources/assets/computercraft/sounds.json",
"projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg",
"projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/**",
"projects/common/src/testMod/resources/data/cctest/structures/**",
"projects/**/src/generated/**",
"projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrade/**",
"projects/common/src/testMod/resources/data/cctest/structure/**",
"projects/*/src/generated/**",
"projects/web/src/htmlTransform/export/index.json",
"projects/web/src/htmlTransform/export/items/minecraft/**",
# GitHub build scripts are CC0. While we could add a header to each file,
# it's unclear if it will break actions or issue templates in some way.
".github/**",
# Example mod is CC0.
"projects/*/src/examples/**"
]
[[annotations]]
@@ -30,23 +35,15 @@ path = [
"doc/images/**",
"package.json",
"package-lock.json",
"projects/common/src/client/resources/computercraft-client.mixins.json",
"projects/*/src/*/resources/*.mixins.json",
"projects/fabric/src/*/resources/fabric.mod.json",
"projects/common/src/main/resources/assets/minecraft/shaders/core/computercraft/monitor_tbo.json",
"projects/common/src/main/resources/computercraft.mixins.json",
"projects/common/src/testMod/resources/computercraft-gametest.mixins.json",
"projects/common/src/testMod/resources/data/computercraft/loot_tables/treasure_disk.json",
"projects/common/src/testMod/resources/pack.mcmeta",
"projects/core/src/main/resources/data/computercraft/lua/rom/modules/command/.ignoreme",
"projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/.ignoreme",
"projects/core/src/main/resources/data/computercraft/lua/rom/modules/turtle/.ignoreme",
"projects/core/src/main/resources/data/computercraft/lua/rom/motd.txt",
"projects/fabric-api/src/main/modJson/fabric.mod.json",
"projects/fabric/src/client/resources/computercraft-client.fabric.mixins.json",
"projects/fabric/src/main/resources/computercraft.fabric.mixins.json",
"projects/fabric/src/main/resources/fabric.mod.json",
"projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json",
"projects/fabric/src/testMod/resources/fabric.mod.json",
"projects/forge/src/client/resources/computercraft-client.forge.mixins.json",
"projects/web/src/frontend/mount/.settings",
"projects/web/src/frontend/mount/example.nfp",
"projects/web/src/frontend/mount/example.nft",
@@ -73,7 +70,7 @@ path = [
]
[[annotations]]
# Community-contributed license files
# Community-contributed language files
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
SPDX-License-Identifier = "LicenseRef-CCPL"
path = [
@@ -87,18 +84,11 @@ path = [
]
[[annotations]]
# Community-contributed license files
# Community-contributed language files
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
SPDX-License-Identifier = "MPL-2.0"
path = "projects/common/src/main/resources/assets/computercraft/lang/**"
[[annotations]]
# GitHub build scripts are CC0. While we could add a header to each file,
# it's unclear if it will break actions or issue templates in some way.
SPDX-FileCopyrightText = "Jonathan Coates <git@squiddev.cc>"
SPDX-License-Identifier = "CC0-1.0"
path = ".github/**"
[[annotations]]
path = ["gradle/wrapper/**"]
SPDX-FileCopyrightText = "Gradle Inc"

View File

@@ -4,13 +4,11 @@
import cc.tweaked.gradle.JUnitExt
import net.fabricmc.loom.api.LoomGradleExtensionAPI
import net.fabricmc.loom.util.gradle.SourceSetHelper
import org.jetbrains.gradle.ext.*
import org.jetbrains.gradle.ext.Application
plugins {
publishing
alias(libs.plugins.taskTree)
alias(libs.plugins.githubRelease)
alias(libs.plugins.gradleVersions)
alias(libs.plugins.versionCatalogUpdate)
@@ -24,21 +22,19 @@ val mcVersion: String by extra
githubRelease {
token(findProperty("githubApiKey") as String? ?: "")
owner.set("cc-tweaked")
repo.set("CC-Tweaked")
targetCommitish.set(cct.gitBranch)
owner = "cc-tweaked"
repo = "CC-Tweaked"
targetCommitish = cct.gitBranch
tagName.set("v$mcVersion-$modVersion")
releaseName.set("[$mcVersion] $modVersion")
body.set(
provider {
"## " + project(":core").file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
.readLines()
.takeWhile { it != "Type \"help changelog\" to see the full version history." }
.joinToString("\n").trim()
},
)
prerelease.set(isUnstable)
tagName = "v$mcVersion-$modVersion"
releaseName = "[$mcVersion] $modVersion"
body = provider {
"## " + project(":core").file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
.readLines()
.takeWhile { it != "Type \"help changelog\" to see the full version history." }
.joinToString("\n").trim()
}
prerelease = isUnstable
}
tasks.publish { dependsOn(tasks.githubRelease) }
@@ -72,7 +68,7 @@ idea.project.settings.runConfigurations {
val fabricProject = evaluationDependsOn(":fabric")
val classPathGroup = fabricProject.extensions.getByType<LoomGradleExtensionAPI>().mods
.joinToString(File.pathSeparator + File.pathSeparator) { modSettings ->
SourceSetHelper.getClasspath(modSettings, project).joinToString(File.pathSeparator) { it.absolutePath }
modSettings.modFiles.joinToString(File.pathSeparator) { it.absolutePath }
}
vmParameters = "-ea -Dfabric.classPathGroups=$classPathGroup"
@@ -117,8 +113,12 @@ idea.project.settings.compiler.javac {
.toMap()
}
versionCatalogUpdate {
sortByKey.set(false)
pin { versions.addAll("fastutil", "guava", "netty", "slf4j") }
keep { keepUnusedLibraries.set(true) }
repositories() {
mavenCentral()
}
versionCatalogUpdate {
sortByKey = false
pin { versions.addAll("fastutil", "guava", "netty", "slf4j") }
keep { keepUnusedVersions = true }
}

View File

@@ -14,18 +14,10 @@ repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.minecraftforge.net") {
name = "Forge"
maven("https://maven.neoforged.net") {
name = "NeoForge"
content {
includeGroup("net.minecraftforge")
includeGroup("net.minecraftforge.gradle")
}
}
maven("https://maven.parchmentmc.org") {
name = "Librarian"
content {
includeGroupByRegex("^org\\.parchmentmc.*")
includeGroup("net.neoforged")
}
}
@@ -33,6 +25,7 @@ repositories {
name = "Fabric"
content {
includeGroup("net.fabricmc")
includeGroup("net.fabricmc.unpick")
}
}
@@ -49,12 +42,10 @@ dependencies {
implementation(libs.kotlin.plugin)
implementation(libs.spotless)
implementation(libs.curseForgeGradle)
implementation(libs.fabric.loom)
implementation(libs.forgeGradle)
implementation(libs.ideaExt)
implementation(libs.librarian)
implementation(libs.minotaur)
implementation(libs.modDevGradle)
implementation(libs.vanillaExtract)
}
@@ -78,7 +69,7 @@ gradlePlugin {
}
versionCatalogUpdate {
sortByKey.set(false)
keep { keepUnusedLibraries.set(true) }
catalogFile.set(file("../gradle/libs.versions.toml"))
sortByKey = false
keep { keepUnusedVersions = true }
catalogFile = file("../gradle/libs.versions.toml")
}

View File

@@ -6,12 +6,12 @@
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.DependencyCheck
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
`java-library`
id("fabric-loom")
id("net.fabricmc.fabric-loom")
id("cc-tweaked.java-convention")
}
@@ -19,18 +19,9 @@ plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
repositories {
maven("https://maven.parchmentmc.org/") {
name = "Parchment"
content {
includeGroup("org.parchmentmc.data")
}
}
}
loom {
splitEnvironmentSourceSets()
splitModDependencies.set(true)
splitModDependencies = true
}
MinecraftConfigurations.setup(project)
@@ -43,27 +34,10 @@ dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
minecraft("com.mojang:minecraft:$mcVersion")
mappings(
loom.layered {
officialMojangMappings()
parchment(
project.dependencies.create(
group = "org.parchmentmc.data",
name = "parchment-${libs.findVersion("parchmentMc").get()}",
version = libs.findVersion("parchment").get().toString(),
ext = "zip",
),
)
},
)
modImplementation(libs.findLibrary("fabric-loader").get())
modImplementation(libs.findLibrary("fabric-api").get())
implementation(libs.findLibrary("fabric-loader").get())
implementation(libs.findLibrary("fabric-api").get())
// Depend on error prone annotations to silence a lot of compile warnings.
compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
}
tasks.ideaSyncTask {
doLast { IdeaRunConfigurations(project).patch() }
compileOnly(libs.findLibrary("errorProne.annotations").get())
}

View File

@@ -6,25 +6,20 @@
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
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.moddev")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
neoForge {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
version = libs.findVersion("neoForge").get().toString()
}
MinecraftConfigurations.setup(project)
@@ -32,13 +27,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

@@ -17,6 +17,8 @@ plugins {
checkstyle
id("com.diffplug.spotless")
id("net.ltgt.errorprone")
// Required for cross-project dependencies in Fabric
id("net.fabricmc.fabric-loom-companion")
}
val modVersion: String by extra
@@ -28,9 +30,9 @@ version = modVersion
base.archivesName.convention("cc-tweaked-$mcVersion-${project.name}")
java {
toolchain {
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
}
toolchain { languageVersion = CCTweakedPlugin.JDK_VERSION }
sourceCompatibility = CCTweakedPlugin.JAVA_VERSION
targetCompatibility = CCTweakedPlugin.JAVA_VERSION
withSourcesJar()
}
@@ -44,27 +46,17 @@ repositories {
exclusiveContent {
forRepositories(mainMaven)
// Include the ForgeGradle repository if present. This requires that ForgeGradle is already present, which we
// enforce in our Forge overlay.
val fg =
project.extensions.findByType(net.minecraftforge.gradle.userdev.DependencyManagementExtension::class.java)
if (fg != null) forRepositories(fg.repository)
filter {
includeGroup("cc.tweaked")
// Things we mirror
includeGroup("com.simibubi.create")
includeGroup("commoble.morered")
includeGroup("dev.architectury")
includeGroup("dev.emi")
includeGroup("maven.modrinth")
includeGroup("me.shedaniel.cloth")
includeGroup("me.shedaniel")
includeGroup("mezz.jei")
includeGroup("org.teavm")
includeModule("com.terraformersmc", "modmenu")
includeModule("me.lucko", "fabric-permissions-api")
}
}
}
@@ -86,19 +78,31 @@ dependencies {
// Configure default JavaCompile tasks with our arguments.
sourceSets.all {
tasks.named(compileJavaTaskName, JavaCompile::class.java) {
// Processing just gives us "No processor claimed any of these annotations", so skip that!
options.compilerArgs.addAll(listOf("-Xlint", "-Xlint:-processing"))
// Explicitly set release, as that limits the APIs we can use to the right version of Java.
options.release = CCTweakedPlugin.JAVA_TARGET.asInt()
options.compilerArgs.addAll(
listOf(
"-Xlint",
// Processing just gives us "No processor claimed any of these annotations", so skip that!
"-Xlint:-processing",
// We violate this pattern too often for it to be a helpful warning. Something to improve one day!
"-Xlint:-this-escape",
),
)
options.errorprone {
check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz
check("InvalidParam", CheckSeverity.OFF) // Broken by records.
check("InlineMeSuggester", CheckSeverity.OFF) // Minecraft uses @Deprecated liberally
// Too many false positives right now. Maybe we need an indirection for it later on.
check("AssignmentExpression", CheckSeverity.OFF) // I'm a bad person.
check("ReferenceEquality", CheckSeverity.OFF)
check("EnumOrdinal", CheckSeverity.OFF) // For now. We could replace most of these with EnumMap.
check("OperatorPrecedence", CheckSeverity.OFF) // For now.
check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid
check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
check("InvalidInlineTag", CheckSeverity.OFF) // Triggered by @snippet. Can be removed on Java 21.
option("UnusedMethod:ExemptingMethodAnnotations", "dan200.computercraft.api.lua.LuaFunction")
check("NullAway", CheckSeverity.ERROR)
option(
@@ -121,7 +125,6 @@ tasks.compileTestJava {
}
}
tasks.withType(JavaCompile::class.java).configureEach {
options.encoding = "UTF-8"
}
@@ -155,7 +158,7 @@ tasks.javadoc {
options {
val stdOptions = this as StandardJavadocDocletOptions
stdOptions.addBooleanOption("Xdoclint:all,-missing", true)
stdOptions.links("https://docs.oracle.com/en/java/javase/17/docs/api/")
stdOptions.links("https://docs.oracle.com/en/java/javase/${CCTweakedPlugin.JAVA_TARGET.asInt()}/docs/api/")
}
}
@@ -169,8 +172,8 @@ tasks.test {
}
tasks.withType(JacocoReport::class.java).configureEach {
reports.xml.required.set(true)
reports.html.required.set(true)
reports.xml.required = true
reports.html.required = true
}
project.plugins.withType(CCTweakedPlugin::class.java) {
@@ -194,30 +197,23 @@ spotless {
fun FormatExtension.defaults() {
endWithNewline()
trimTrailingWhitespace()
indentWithSpaces(4)
leadingTabsToSpaces(4)
}
java {
defaults()
importOrder("", "javax|java", "\\#")
removeUnusedImports()
}
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",
)
kotlinGradle {
defaults()
ktlint().editorConfigOverride(ktlintConfig)
ktlint()
}
kotlin {
defaults()
ktlint().editorConfigOverride(ktlintConfig)
ktlint()
}
}
@@ -226,6 +222,5 @@ idea.module {
// Force Gradle to write to inherit the output directory from the parent, instead of writing to out/xxx/classes.
// This is required for Loom, and we patch Forge's run configurations to work there.
// TODO: Submit a patch to Forge to support ProjectRootManager.
inheritOutputDirs = true
}

View File

@@ -1,25 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.CCTweakedPlugin
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
}
kotlin {
jvmToolchain {
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
}
}
tasks.withType(KotlinCompile::class.java).configureEach {
// So technically we shouldn't need to do this as the toolchain sets it above. However, the option only appears
// to be set when the task executes, so doesn't get picked up by IDEs.
kotlinOptions.jvmTarget = when {
CCTweakedPlugin.JAVA_VERSION.asInt() > 8 -> CCTweakedPlugin.JAVA_VERSION.toString()
else -> "1.${CCTweakedPlugin.JAVA_VERSION.asInt()}"
}
}

View File

@@ -2,11 +2,9 @@
//
// SPDX-License-Identifier: MPL-2.0
import net.darkhax.curseforgegradle.TaskPublishCurseForge
import cc.tweaked.gradle.setProvider
plugins {
id("net.darkhax.curseforgegradle")
id("com.modrinth.minotaur")
id("cc-tweaked.publishing")
}
@@ -25,34 +23,17 @@ val isUnstable = project.properties["isUnstable"] == "true"
val modVersion: String by extra
val mcVersion: String by extra
val publishCurseForge by tasks.registering(TaskPublishCurseForge::class) {
group = PublishingPlugin.PUBLISH_TASK_GROUP
description = "Upload artifacts to CurseForge"
apiToken = findProperty("curseForgeApiKey") ?: ""
enabled = apiToken != ""
val mainFile = upload("282001", modPublishing.output)
mainFile.changelog =
"Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion)."
mainFile.changelogType = "markdown"
mainFile.releaseType = if (isUnstable) "alpha" else "release"
mainFile.gameVersions.add(mcVersion)
}
tasks.publish { dependsOn(publishCurseForge) }
modrinth {
token.set(findProperty("modrinthApiKey") as String? ?: "")
projectId.set("gu7yAYhd")
versionNumber.set(modVersion)
versionName.set(modVersion)
versionType.set(if (isUnstable) "alpha" else "release")
token = findProperty("modrinthApiKey") as String? ?: ""
projectId = "gu7yAYhd"
versionNumber = modVersion
versionName = modVersion
versionType = if (isUnstable) "alpha" else "release"
uploadFile.setProvider(modPublishing.output)
gameVersions.add(mcVersion)
changelog.set("Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion).")
changelog = "Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion)."
syncBodyFrom.set(provider { rootProject.file("doc/mod-page.md").readText() })
syncBodyFrom = provider { rootProject.file("doc/mod-page.md").readText() }
}
tasks.publish { dependsOn(tasks.modrinth) }

View File

@@ -2,44 +2,35 @@
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
/**
* Sets up the configurations for writing game tests.
*
* See notes in [cc.tweaked.gradle.MinecraftConfigurations] for the general design behind these cursed ideas.
*/
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.MinecraftConfigurations
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
plugins {
id("cc-tweaked.kotlin-convention")
kotlin("jvm")
id("cc-tweaked.java-convention")
}
val main = sourceSets["main"]
val client = sourceSets["client"]
// Both testMod and testFixtures inherit from the main and client classpath, just so we have access to Minecraft classes.
val testMod by sourceSets.creating {
compileClasspath += main.compileClasspath + client.compileClasspath
runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
}
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.DATAGEN)
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.EXAMPLES)
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.TEST_MOD)
configurations {
named(testMod.compileClasspathConfigurationName) {
shouldResolveConsistentlyWith(compileClasspath.get())
}
// Set up generated resources
sourceSets.main { resources.srcDir("src/generated/resources") }
sourceSets.named("examples") { resources.srcDir("src/examples/generatedResources") }
named(testMod.runtimeClasspathConfigurationName) {
shouldResolveConsistentlyWith(runtimeClasspath.get())
}
}
// Like the main test configurations, we're safe to depend on source set outputs.
dependencies {
add(testMod.implementationConfigurationName, main.output)
add(testMod.implementationConfigurationName, client.output)
}
// Make sure our examples compile.
tasks.check { dependsOn(tasks.named("compileExamplesJava")) }
// Similar to java-test-fixtures, but tries to avoid putting the obfuscated jar on the classpath.
@@ -62,3 +53,5 @@ dependencies {
testImplementation(testFixtures(project))
}
kotlin.compilerOptions.jvmTarget = CCTweakedPlugin.KOTLIN_TARGET

View File

@@ -12,25 +12,26 @@ publishing {
register<MavenPublication>("maven") {
artifactId = base.archivesName.get()
from(components["java"])
suppressAllPomMetadataWarnings()
pom {
name.set("CC: Tweaked")
description.set("CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.")
url.set("https://github.com/cc-tweaked/CC-Tweaked")
name = "CC: Tweaked"
description = "CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft."
url = "https://github.com/cc-tweaked/CC-Tweaked"
scm {
url.set("https://github.com/cc-tweaked/CC-Tweaked.git")
url = "https://github.com/cc-tweaked/CC-Tweaked.git"
}
issueManagement {
system.set("github")
url.set("https://github.com/cc-tweaked/CC-Tweaked/issues")
system = "github"
url = "https://github.com/cc-tweaked/CC-Tweaked/issues"
}
licenses {
license {
name.set("ComputerCraft Public License, Version 1.0")
url.set("https://github.com/cc-tweaked/CC-Tweaked/blob/HEAD/LICENSE")
name = "ComputerCraft Public License, Version 1.0"
url = "https://github.com/cc-tweaked/CC-Tweaked/blob/HEAD/LICENSE"
}
}
}

View File

@@ -10,7 +10,7 @@ import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("cc-tweaked.java-convention")
id("cc.tweaked.vanilla-extract")
id("net.fabricmc.fabric-loom")
}
plugins.apply(CCTweakedPlugin::class.java)
@@ -19,22 +19,19 @@ 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())
loom {
splitEnvironmentSourceSets()
splitModDependencies = true
}
dependencies {
minecraft("com.mojang:minecraft:$mcVersion")
// Depend on error prone annotations to silence a lot of compile warnings.
compileOnly(libs.findLibrary("errorProne.annotations").get())
}
MinecraftConfigurations.setupBasic(project)
MinecraftConfigurations.setup(project)
extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = null)

View File

@@ -10,14 +10,9 @@ 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
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.compile.JavaCompile
@@ -25,7 +20,6 @@ import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.gradle.language.jvm.tasks.ProcessResources
import org.gradle.process.JavaForkOptions
import org.gradle.testing.jacoco.plugins.JacocoCoverageReport
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
import org.gradle.testing.jacoco.tasks.JacocoReport
@@ -36,54 +30,40 @@ import java.io.IOException
import java.net.URI
import java.util.regex.Pattern
abstract class CCTweakedExtension(
private val project: Project,
private val fs: FileSystemOperations,
) {
abstract class CCTweakedExtension(private val project: Project) {
/** Get the hash of the latest git commit. */
val gitHash: Provider<String> = gitProvider(project, "<no git hash>") {
ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "HEAD").trim()
}
val gitHash: Provider<String> =
gitProvider("<no git commit>", listOf("rev-parse", "HEAD")) { it.trim() }
/** Get the current git branch. */
val gitBranch: Provider<String> = gitProvider(project, "<no git branch>") {
ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "--abbrev-ref", "HEAD")
.trim()
}
val gitBranch: Provider<String> =
gitProvider("<no git branch>", listOf("rev-parse", "--abbrev-ref", "HEAD")) { it.trim() }
/** Get a list of all contributors to the project. */
val gitContributors: Provider<List<String>> = gitProvider(project, listOf()) {
ProcessHelpers.captureLines(
"git", "-C", project.rootProject.projectDir.absolutePath, "shortlog", "-ns",
"--group=author", "--group=trailer:co-authored-by", "HEAD",
)
.asSequence()
.map {
val matcher = COMMIT_COUNTS.matcher(it)
matcher.find()
matcher.group(1)
}
.filter { !IGNORED_USERS.contains(it) }
.toList()
.sortedWith(String.CASE_INSENSITIVE_ORDER)
}
val gitContributors: Provider<List<String>> =
gitProvider(listOf(), listOf("shortlog", "-ns", "--group=author", "--group=trailer:co-authored-by", "HEAD")) { input ->
input.lineSequence()
.filter { it.isNotEmpty() }
.map {
val matcher = COMMIT_COUNTS.matcher(it)
matcher.find()
matcher.group(1)
}
.filter { !IGNORED_USERS.contains(it) }
.toList()
.sortedWith(String.CASE_INSENSITIVE_ORDER)
}
/**
* References to other sources
*/
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() }
}
@@ -109,14 +89,13 @@ abstract class CCTweakedExtension(
val otherJava = otherProject.extensions.getByType(JavaPluginExtension::class.java)
val main = otherJava.sourceSets.getByName("main")
val client = otherJava.sourceSets.getByName("client")
val testMod = otherJava.sourceSets.findByName("testMod")
val testFixtures = otherJava.sourceSets.findByName("testFixtures")
// Pull in sources from the other project.
extendSourceSet(otherProject, main)
extendSourceSet(otherProject, client)
if (testMod != null) extendSourceSet(otherProject, testMod)
if (testFixtures != null) extendSourceSet(otherProject, testFixtures)
for (sourceSet in listOf(MinecraftConfigurations.DATAGEN, MinecraftConfigurations.EXAMPLES, MinecraftConfigurations.TEST_MOD, "testFixtures")) {
otherJava.sourceSets.findByName(sourceSet)?.let { extendSourceSet(otherProject, it) }
}
// The extra source-processing tasks should include these files too.
project.tasks.named(main.javadocTaskName, Javadoc::class.java) { source(main.allJava, client.allJava) }
@@ -179,23 +158,19 @@ abstract class CCTweakedExtension(
}
fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions {
val classDump = project.layout.buildDirectory.dir("jacocoClassDump/${task.name}")
val reportTaskName = "jacoco${task.name.capitalise()}Report"
val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
task.configure {
finalizedBy(reportTaskName)
doFirst("Clean class dump directory") { fs.delete { delete(classDump) } }
jacoco.applyTo(this)
extensions.configure(JacocoTaskExtension::class.java) {
includes = listOf("dan200.computercraft.*")
classDumpDir = classDump.get().asFile
// Older versions of modlauncher don't include a protection domain (and thus no code
// source). Jacoco skips such classes by default, so we need to explicitly include them.
isIncludeNoLocationClasses = true
excludes = listOf(
"dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime.
"dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them.
)
}
}
@@ -204,15 +179,11 @@ abstract class CCTweakedExtension(
description = "Generates code coverage report for the ${task.name} task."
executionData(task.get())
classDirectories.from(classDump)
// Don't want to use sourceSets(...) here as we have a custom class directory.
for (ref in sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories)
}
project.extensions.configure(ReportingExtension::class.java) {
reports.register("${task.name}CodeCoverageReport", JacocoCoverageReport::class.java) {
testType.set(TestSuiteType.INTEGRATION_TEST)
// Don't want to use sourceSets(...) here as we don't use all class directories.
for (ref in this@CCTweakedExtension.sourceDirectories.get()) {
sourceDirectories.from(ref.sourceSet.allSource.sourceDirectories)
if (ref.classes) classDirectories.from(ref.sourceSet.output)
}
}
}
@@ -252,40 +223,31 @@ abstract class CCTweakedExtension(
).resolve().single()
}
/**
* Exclude a dependency from being published in Maven.
*/
fun exclude(dep: Dependency) {
excludedDeps.add(dep)
}
private fun <T: Any> gitProvider(default: T, command: List<String>, process: (String) -> T): Provider<T> {
val baseResult = project.providers.exec {
commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command
}
/**
* Configure a [MavenDependencySpec].
*/
fun configureExcludes(spec: MavenDependencySpec) {
for (dep in excludedDeps.get()) spec.exclude(dep)
return project.provider {
val res = try {
baseResult.standardOutput.asText.get()
} catch (e: IOException) {
project.logger.error("Cannot read Git repository: ${e.message}", e)
return@provider default
} catch (e: GradleException) {
project.logger.error("Cannot read Git repository: ${e.message}", e)
return@provider default
}
process(res)
}
}
companion object {
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
private val IGNORED_USERS = setOf(
"GitHub", "Daniel Ratcliffe", "Weblate",
"GitHub", "Daniel Ratcliffe", "NotSquidDev", "Weblate",
)
private fun <T> gitProvider(project: Project, default: T, supplier: () -> T): Provider<T> {
return project.provider {
try {
supplier()
} catch (e: IOException) {
project.logger.error("Cannot read Git repository: ${e.message}")
default
} catch (e: GradleException) {
project.logger.error("Cannot read Git repository: ${e.message}")
default
}
}
}
private val isIdeSync: Boolean
get() = java.lang.Boolean.parseBoolean(System.getProperty("idea.sync.active", "false"))
}

View File

@@ -4,6 +4,7 @@
package cc.tweaked.gradle
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
@@ -13,6 +14,7 @@ import org.gradle.plugins.ide.idea.model.IdeaModel
import org.jetbrains.gradle.ext.IdeaExtPlugin
import org.jetbrains.gradle.ext.runConfigurations
import org.jetbrains.gradle.ext.settings
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
/**
* Configures projects to match a shared configuration.
@@ -42,6 +44,17 @@ class CCTweakedPlugin : Plugin<Project> {
}
companion object {
val JAVA_VERSION = JavaLanguageVersion.of(17)
/**
* The version we run with. We use Java 25 here, as our Gradle build requires that.
*/
val JDK_VERSION = JavaLanguageVersion.of(25)
/**
* The Java version we target. Should be the same as what Minecraft uses.
*/
val JAVA_TARGET = JavaLanguageVersion.of(25)
val JAVA_VERSION = JavaVersion.toVersion(JAVA_TARGET.asInt())
val KOTLIN_TARGET = JvmTarget.fromTarget(JAVA_TARGET.toString())
}
}

View File

@@ -22,19 +22,19 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin
abstract class DependencyCheck : DefaultTask() {
@get:Input
abstract val configuration: ListProperty<Configuration>
protected abstract val dependencies: ListProperty<DependencyResult>
/**
* A mapping of module coordinates (`group:module`) to versions, overriding the requested version.
*/
@get:Input
abstract val overrides: MapProperty<String, String>
protected abstract val overrides: MapProperty<String, String>
init {
description = "Check :core's dependencies are consistent with Minecraft's."
group = LifecycleBasePlugin.VERIFICATION_GROUP
configuration.finalizeValueOnRead()
dependencies.finalizeValueOnRead()
overrides.finalizeValueOnRead()
}
@@ -45,13 +45,23 @@ abstract class DependencyCheck : DefaultTask() {
overrides.putAll(project.provider { mutableMapOf(module.get().module.toString() to version) })
}
fun override(group: String, module: String, version: String) {
overrides.put("$group:$module", version)
}
/**
* Add a configuration to check.
*/
fun configuration(configuration: Provider<Configuration>) {
// We can't store the Configuration in the cache, so store the resolved dependencies instead.
dependencies.addAll(configuration.map { it.incoming.resolutionResult.allDependencies })
}
@TaskAction
fun run() {
var ok = true
for (configuration in configuration.get()) {
configuration.incoming.resolutionResult.allDependencies {
if (!check(this@allDependencies)) ok = false
}
for (configuration in dependencies.get()) {
if (!check(configuration)) ok = false
}
if (!ok) {

View File

@@ -5,10 +5,8 @@
package cc.tweaked.gradle
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.AbstractExecTask
import org.gradle.api.tasks.OutputDirectory
import java.io.File
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
@get:OutputDirectory

View File

@@ -9,6 +9,7 @@ import org.gradle.api.file.FileSystemLocation
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.JavaExec
import org.gradle.kotlin.dsl.getByName
import org.gradle.process.BaseExecSpec
import org.gradle.process.JavaExecSpec
import org.gradle.process.ProcessForkOptions
@@ -46,6 +47,21 @@ fun JavaExec.copyToFull(spec: JavaExec) {
copyToExec(spec)
}
/**
* Base this [JavaExec] task on an existing task.
*/
fun JavaExec.copyFromTask(task: JavaExec) {
for (dep in task.dependsOn) dependsOn(dep)
task.copyToFull(this)
}
/**
* Base this [JavaExec] task on an existing task.
*/
fun JavaExec.copyFromTask(task: String) {
copyFromTask(project.tasks.getByName<JavaExec>(task))
}
/**
* Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo].
*/
@@ -124,7 +140,7 @@ class CloseScope : AutoCloseable {
}
/** Proxy method to avoid overload ambiguity. */
fun <T> Property<T>.setProvider(provider: Provider<out T>) = set(provider)
fun <T: Any> 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

View File

@@ -1,26 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import net.minecraftforge.gradle.common.util.RunConfig
import net.minecraftforge.gradle.common.util.runs.setRunConfigInternal
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.JavaExec
import org.gradle.jvm.toolchain.JavaToolchainService
import java.nio.file.Files
/**
* Set [JavaExec] task to run a given [RunConfig].
*/
fun JavaExec.setRunConfig(config: RunConfig) {
dependsOn("prepareRuns")
setRunConfigInternal(project, this, config)
doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
javaLauncher.set(
project.extensions.getByType(JavaToolchainService::class.java)
.launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain),
)
}

View File

@@ -1,174 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import org.gradle.api.Project
import org.gradle.api.logging.Logging
import org.w3c.dom.Attr
import org.w3c.dom.Document
import org.w3c.dom.Node
import org.xml.sax.InputSource
import java.nio.file.Files
import java.nio.file.Path
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory
/**
* Patches up run configurations from ForgeGradle and Loom.
*
* Would be good to PR some (or all) of these changes upstream at some point.
*
* @see net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask
* @see net.minecraftforge.gradle.common.util.runs.IntellijRunGenerator
*/
internal class IdeaRunConfigurations(project: Project) {
private val rootProject = project.rootProject
private val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
private val xpath = XPathFactory.newInstance().newXPath()
private val writer = TransformerFactory.newInstance().newTransformer()
private val ideaDir = rootProject.file(".idea/")
private val buildDir: Lazy<String?> = lazy {
val ideaMisc = ideaDir.resolve("misc.xml")
try {
val doc = Files.newBufferedReader(ideaMisc.toPath()).use {
documentBuilder.parse(InputSource(it))
}
val node =
xpath.evaluate("//component[@name=\"ProjectRootManager\"]/output", doc, XPathConstants.NODE) as Node
val attr = node.attributes.getNamedItem("url") as Attr
attr.value.removePrefix("file://")
} catch (e: Exception) {
LOGGER.error("Failed to find root directory", e)
null
}
}
fun patch() = synchronized(LOCK) {
val runConfigDir = ideaDir.resolve("runConfigurations")
if (!runConfigDir.isDirectory) return
Files.list(runConfigDir.toPath()).use {
for (configuration in it) {
val filename = configuration.fileName.toString();
when {
filename.endsWith("_fabric.xml") -> patchFabric(configuration)
filename.startsWith("forge_") && filename.endsWith(".xml") -> patchForge(configuration)
else -> {}
}
}
}
}
private fun patchFabric(path: Path) = withXml(path) {
setXml("//configuration", "folderName") { "Fabric" }
}
private fun patchForge(path: Path) = withXml(path) {
val configId = path.fileName.toString().removePrefix("forge_").removeSuffix(".xml")
val sourceSet = forgeConfigs[configId]
if (sourceSet == null) {
LOGGER.error("[{}] Cannot map run configuration to a known source set", path)
return@withXml
}
setXml("//configuration", "folderName") { "Forge" }
setXml("//configuration/module", "name") { "${rootProject.name}.forge.$sourceSet" }
if (buildDir.value == null) return@withXml
setXml("//configuration/envs/env[@name=\"MOD_CLASSES\"]", "value") { classpath ->
val classes = classpath!!.split(':')
val newClasses = mutableListOf<String>()
fun appendUnique(x: String) {
if (!newClasses.contains(x)) newClasses.add(x)
}
for (entry in classes) {
if (!entry.contains("/out/")) {
appendUnique(entry)
continue
}
val match = CLASSPATH_ENTRY.matchEntire(entry)
if (match != null) {
val modId = match.groups["modId"]!!.value
val proj = match.groups["proj"]!!.value
var component = match.groups["component"]!!.value
if (component == "production") component = "main"
appendUnique(forgeModEntry(modId, proj, component))
} else {
LOGGER.warn("[{}] Unknown classpath entry {}", path, entry)
appendUnique(entry)
}
}
// Ensure common code is on the classpath
for (proj in listOf("common", "common-api")) {
for (component in listOf("main", "client")) {
appendUnique(forgeModEntry("computercraft", proj, component))
}
}
if (newClasses.any { it.startsWith("cctest%%") }) {
appendUnique(forgeModEntry("cctest", "core", "testFixtures"))
appendUnique(forgeModEntry("cctest", "common", "testFixtures"))
appendUnique(forgeModEntry("cctest", "common", "testMod"))
}
newClasses.joinToString(":")
}
}
private fun forgeModEntry(mod: String, project: String, component: String) =
"$mod%%${buildDir.value}/production/${rootProject.name}.$project.$component"
private fun LocatedDocument.setXml(xpath: String, attribute: String, value: (String?) -> String) {
val node = this@IdeaRunConfigurations.xpath.evaluate(xpath, document, XPathConstants.NODE) as Node?
if (node == null) {
LOGGER.error("[{}] Cannot find {}", path.fileName, xpath)
return
}
val attr = node.attributes.getNamedItem(attribute) as Attr? ?: document.createAttribute(attribute)
val oldValue = attr.value
attr.value = value(attr.value)
node.attributes.setNamedItem(attr)
if (oldValue != attr.value) {
LOGGER.info("[{}] Setting {}@{}:\n Old: {}\n New: {}", path.fileName, xpath, attribute, oldValue, attr.value)
}
}
private fun withXml(path: Path, run: LocatedDocument.() -> Unit) {
val doc = Files.newBufferedReader(path).use { documentBuilder.parse(InputSource(it)) }
run(LocatedDocument(path, doc))
Files.newBufferedWriter(path).use { writer.transform(DOMSource(doc), StreamResult(it)) }
}
private class LocatedDocument(val path: Path, val document: Document)
companion object {
private val LOGGER = Logging.getLogger(IdeaRunConfigurations::class.java)
private val LOCK = Any()
private val CLASSPATH_ENTRY =
Regex("(?<modId>[a-z]+)%%\\\$PROJECT_DIR\\\$/projects/(?<proj>[a-z-]+)/out/(?<component>\\w+)/(?<type>[a-z]+)\$")
private val forgeConfigs = mapOf(
"runClient" to "client",
"runData" to "main",
"runGameTestServer" to "testMod",
"runServer" to "main",
"runTestClient" to "testMod",
)
}
}

View File

@@ -77,13 +77,8 @@ class IlluaminatePlugin : Plugin<Project> {
else -> error("Unsupported architecture '$osArch' for illuaminate")
}
return project.dependencies.create(
mapOf(
"group" to "cc.squiddev",
"name" to "illuaminate",
"version" to version,
"ext" to "$os-$arch$suffix",
),
return project.dependencyFactory.create(
"cc.squiddev", "illuaminate", version, null, "$os-$arch$suffix",
)
}
}

View File

@@ -1,73 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
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
/**
* A dependency in a POM file.
*/
data class MavenDependency(val groupId: String?, val artifactId: String?, val version: String?, val scope: String?)
/**
* A spec specifying which dependencies to include/exclude.
*/
class MavenDependencySpec {
private val excludeSpecs = mutableListOf<Spec<MavenDependency>>()
fun exclude(spec: Spec<MavenDependency>) {
excludeSpecs.add(spec)
}
fun exclude(dep: Dependency) {
exclude {
// 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) &&
(name.isNullOrEmpty() || name == it.artifactId) &&
(dep.version.isNullOrEmpty() || dep.version == it.version)
}
}
fun exclude(dep: MinimalExternalModuleDependency) {
exclude {
dep.module.group == it.groupId && dep.module.name == it.artifactId
}
}
fun isIncluded(dep: MavenDependency) = !excludeSpecs.any { it.isSatisfiedBy(dep) }
}
/**
* Configure dependencies present in this publication's POM file.
*
* While this approach is very ugly, it's the easiest way to handle it!
*/
fun MavenPublication.mavenDependencies(action: MavenDependencySpec.() -> Unit) {
val spec = MavenDependencySpec()
action(spec)
pom.withXml {
val dependencies = XmlUtil.findChild(asNode(), "dependencies") ?: return@withXml
dependencies.children().map { it as groovy.util.Node }.forEach {
val dep = MavenDependency(
groupId = XmlUtil.findChild(it, "groupId")?.text(),
artifactId = XmlUtil.findChild(it, "artifactId")?.text(),
version = XmlUtil.findChild(it, "version")?.text(),
scope = XmlUtil.findChild(it, "scope")?.text(),
)
if (!spec.isIncluded(dep)) it.parent().remove(it)
}
}
}

View File

@@ -24,7 +24,6 @@ class MinecraftConfigurations private constructor(private val project: Project)
private val java = project.extensions.getByType(JavaPluginExtension::class.java)
private val sourceSets = java.sourceSets
private val configurations = project.configurations
private val objects = project.objects
private val main = sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]
private val test = sourceSets[SourceSet.TEST_SOURCE_SET_NAME]
@@ -37,17 +36,10 @@ class MinecraftConfigurations private constructor(private val project: Project)
val client = sourceSets.maybeCreate("client")
// Ensure the client classpaths behave the same as the main ones.
configurations.named(client.compileClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.compileClasspathConfigurationName])
}
configurations.named(client.runtimeClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.runtimeClasspathConfigurationName])
}
consistentWithMain(client)
// Set up an API configuration for clients (to ensure it's consistent with the main source set).
val clientApi = configurations.maybeCreate(client.apiConfigurationName).apply {
isVisible = false
isCanBeConsumed = false
isCanBeResolved = false
}
@@ -85,6 +77,16 @@ class MinecraftConfigurations private constructor(private val project: Project)
setupBasic()
}
private fun consistentWithMain(sourceSet: SourceSet) {
configurations.named(sourceSet.compileClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.compileClasspathConfigurationName])
}
configurations.named(sourceSet.runtimeClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.runtimeClasspathConfigurationName])
}
}
private fun setupBasic() {
val client = sourceSets["client"]
@@ -96,13 +98,30 @@ class MinecraftConfigurations private constructor(private val project: 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))
configuration(configurations.named(main.runtimeClasspathConfigurationName))
configuration(configurations.named(client.runtimeClasspathConfigurationName))
}
project.tasks.named("check") { dependsOn(checkDependencyConsistency) }
}
/**
* Create a new configuration that pulls in the main and client classes from the mod.
*/
private fun createDerivedConfiguration(name: String) {
val client = sourceSets["client"]
val sourceSet = sourceSets.create(name)
sourceSet.compileClasspath += main.compileClasspath + client.compileClasspath
sourceSet.runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
consistentWithMain(sourceSet)
project.dependencies.add(sourceSet.implementationConfigurationName, main.output)
project.dependencies.add(sourceSet.implementationConfigurationName, client.output)
}
companion object {
const val DATAGEN = "datagen"
const val EXAMPLES = "examples"
const val TEST_MOD = "testMod"
fun setupBasic(project: Project) {
MinecraftConfigurations(project).setupBasic()
}
@@ -110,6 +129,10 @@ class MinecraftConfigurations private constructor(private val project: Project)
fun setup(project: Project) {
MinecraftConfigurations(project).setup()
}
fun createDerivedConfiguration(project: Project, name: String) {
MinecraftConfigurations(project).createDerivedConfiguration(name)
}
}
}

View File

@@ -4,7 +4,7 @@
package cc.tweaked.gradle
import net.minecraftforge.gradle.common.util.RunConfig
import net.neoforged.moddevgradle.internal.RunGameTask
import org.gradle.api.GradleException
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.invocation.Gradle
@@ -19,6 +19,7 @@ import java.nio.file.Files
import java.util.concurrent.TimeUnit
import java.util.function.Supplier
import javax.inject.Inject
import kotlin.collections.set
import kotlin.random.Random
/**
@@ -65,11 +66,19 @@ abstract class ClientJavaExec : JavaExec() {
setTestProperties()
}
fun copyFromForge(path: String) = copyFromForge(project.tasks.getByName(path, RunGameTask::class))
/**
* Set this task to run a given [RunConfig].
* Set this task to run a given [RunGameTask].
*/
fun setRunConfig(config: RunConfig) {
(this as JavaExec).setRunConfig(config)
fun copyFromForge(task: RunGameTask) {
copyFrom(task)
// Eagerly evaluate the behaviour of RunGameTask.exec
environment.putAll(task.environmentProperty.get())
classpath(task.classpathProvider)
workingDir = task.gameDirectory.get().asFile
setTestProperties() // setRunConfig may clobber some properties, ensure everything is set.
}
@@ -117,6 +126,23 @@ abstract class ClientJavaExec : JavaExec() {
}
}
/**
* Configure Iris to use Complementary Shaders.
*/
fun withComplementaryShaders() {
val cct = project.extensions.getByType(CCTweakedExtension::class.java)
withFileFrom(workingDir.resolve("shaderpacks/ComplementaryShaders_v4.6.zip")) {
cct.downloadFile("Complementary Shaders", "https://edge.forgecdn.net/files/3951/170/ComplementaryShaders_v4.6.zip")
}
withFileContents(workingDir.resolve("config/iris.properties")) {
"""
enableShaders=true
shaderPack=ComplementaryShaders_v4.6.zip
""".trimIndent()
}
}
@TaskAction
override fun exec() {
Files.createDirectories(workingDir.toPath())

View File

@@ -11,7 +11,9 @@ import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import java.io.File
import javax.inject.Inject
class NodePlugin : Plugin<Project> {
override fun apply(project: Project) {
@@ -43,9 +45,12 @@ abstract class NpmInstall : DefaultTask() {
@get:OutputDirectory
val nodeModules: Provider<Directory> = projectRoot.dir("node_modules")
@get:Inject
protected abstract val execOperations: ExecOperations
@TaskAction
fun install() {
project.exec {
execOperations.exec {
commandLine(ProcessHelpers.getExecutable("npm"), "ci")
workingDir = projectRoot.get().asFile
}

View File

@@ -4,45 +4,10 @@
package cc.tweaked.gradle
import org.codehaus.groovy.runtime.ProcessGroovyMethods
import org.gradle.api.GradleException
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
internal object ProcessHelpers {
fun startProcess(vararg command: String): Process {
// Something randomly passes in "GIT_DIR=" as an environment variable which clobbers everything else. Don't
// inherit the environment array!
return ProcessBuilder()
.command(*command)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.also { it.environment().clear() }
.start()
}
fun captureOut(vararg command: String): String {
val process = startProcess(*command)
process.outputStream.close()
val result = ProcessGroovyMethods.getText(process)
process.waitForOrThrow("Failed to run command")
return result
}
fun captureLines(vararg command: String): List<String> {
val process = startProcess(*command)
process.outputStream.close()
val out = BufferedReader(InputStreamReader(process.inputStream, StandardCharsets.UTF_8)).use { reader ->
reader.lines().filter { it.isNotEmpty() }.toList()
}
ProcessGroovyMethods.closeStreams(process)
process.waitForOrThrow("Failed to run command")
return out
}
fun onPath(name: String): Boolean {
val path = System.getenv("PATH") ?: return false
return path.splitToSequence(File.pathSeparator).any { File(it, name).exists() }

View File

@@ -1,16 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import groovy.util.Node
import groovy.util.NodeList
object XmlUtil {
fun findChild(node: Node, name: String): Node? = when (val child = node.get(name)) {
is Node -> child
is NodeList -> child.singleOrNull() as Node?
else -> null
}
}

View File

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

View File

@@ -21,6 +21,15 @@ SPDX-License-Identifier: MPL-2.0
<property name="file" value="${config_loc}/suppressions.xml" />
</module>
<!--
Checkstyle doesn't support @snippet (https://github.com/checkstyle/checkstyle/issues/11455),
so suppress warnings nearby
-->
<module name="SuppressWithNearbyTextFilter">
<property name="nearbyTextPattern" value="@snippet" />
<property name="lineRange" value="20" />
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="render_old"/>
</module>
@@ -92,7 +101,10 @@ SPDX-License-Identifier: MPL-2.0
<module name="InvalidJavadocPosition" />
<module name="JavadocBlockTagLocation" />
<module name="JavadocMethod"/>
<module name="JavadocType"/>
<module name="JavadocType">
<!-- Seems to complain about @hidden!? -->
<property name="allowUnknownTags" value="true" />
</module>
<module name="JavadocStyle">
<property name="checkHtml" value="false" />
</module>
@@ -117,14 +129,14 @@ SPDX-License-Identifier: MPL-2.0
<module name="LocalFinalVariableName" />
<module name="LocalVariableName" />
<module name="MemberName">
<property name="format" value="^\$?[a-z][a-zA-Z0-9]*$" />
<property name="format" value="^(computercraft\$|\$)?[a-z][a-zA-Z0-9]*$" />
</module>
<module name="MethodName">
<property name="format" value="^(computercraft\$)?[a-z][a-zA-Z0-9]*$" />
</module>
<module name="MethodTypeParameterName" />
<module name="PackageName">
<property name="format" value="^(dan200\.computercraft|cc\.tweaked)(\.[a-z][a-z0-9]*)*" />
<property name="format" value="^(dan200\.computercraft|cc\.tweaked|com\.example\.examplemod)(\.[a-z][a-z0-9]*)*" />
</module>
<module name="ParameterName" />
<module name="StaticVariableName">
@@ -143,7 +155,10 @@ SPDX-License-Identifier: MPL-2.0
<module name="NoWhitespaceAfter">
<property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP,METHOD_REF" />
</module>
<module name="NoWhitespaceBefore" />
<module name="NoWhitespaceBefore">
<!-- Allow whitespace before "..." for @Nullable annotations -->
<property name="tokens" value="COMMA,SEMI,POST_INC,POST_DEC,LABELED_STAT" />
</module>
<!-- TODO: Decide on an OperatorWrap style. -->
<module name="ParenPad" />
<module name="SeparatorWrap">

View File

@@ -22,4 +22,7 @@ SPDX-License-Identifier: MPL-2.0
<!-- Allow underscores in our test classes. -->
<suppress checks="MethodName" files=".*(Contract|Test).java" />
<!-- Allow underscores in Mixin classes -->
<suppress checks="TypeName" files=".*[\\/]mixin[\\/].*.java" />
</suppressions>

View File

@@ -1,16 +1,18 @@
# SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
#
# SPDX-License-Identifier: MPL-2.0
files:
- source: projects/common/src/generated/resources/assets/computercraft/lang/en_us.json
translation: /projects/common/src/main/resources/assets/computercraft/lang/%locale_with_underscore%.json
languages_mapping:
locale_with_underscore:
cs: cs_cs # Czech
cs: cs_cz # Czech
da: da_dk # Danish
de: de_de # German
es-ES: es_es # Spanish
fr: fr_fr # French
hu: hu_hu # Hungarian
it: it_it # Italian
ja: ja_jp # Japanese
ko: ko_kr # Korean

View File

@@ -8,7 +8,7 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
The [`monitor_resize`] event is fired when an adjacent or networked monitor's size is changed.
The [`monitor_resize`] event is fired when an adjacent or networked [monitor's][`monitor`] size is changed.
## Return Values
1. [`string`]: The event name.

View File

@@ -8,7 +8,7 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
The [`monitor_touch`] event is fired when an adjacent or networked Advanced Monitor is right-clicked.
The [`monitor_touch`] event is fired when an adjacent or networked [Advanced Monitor][`monitor`] is right-clicked.
## Return Values
1. [`string`]: The event name.

View File

@@ -8,7 +8,7 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
The [`event!redstone`] event is fired whenever any redstone inputs on the computer change.
The [`event!redstone`] event is fired whenever any redstone inputs on the computer or [relay][`redstone_relay`] change.
## Return values
1. [`string`]: The event name.
@@ -21,3 +21,7 @@ while true do
print("A redstone input has changed!")
end
```
## See also
- [The `redstone` API on computers][`module!redstone`]
- [The `redstone_relay` peripheral][`redstone_relay`]

View File

@@ -9,85 +9,68 @@ SPDX-License-Identifier: MPL-2.0
-->
# Setting up GPS
The [`gps`] API allows computers and turtles to find their current position using wireless modems.
The [`gps`] API allows a computer to find its current position using a [wireless modem][`modem`]. This works by
communicating with other computers (called *GPS hosts*) that already know their position, finding the distance to those
computers (with [`modem_message`]), and using that to derive its position from theirs (with a process known as
[trilateration](https://en.wikipedia.org/wiki/Trilateration).
In order to use GPS, you'll need to set up multiple *GPS hosts*. These are computers running the special `gps host`
program, which tell other computers the host's position. Several hosts running together are known as a *GPS
constellation*.
In order for this to work, we need our GPS hosts set up in a specific pattern, each one differing in position on at
least one axis. This guide takes you through the process of setting up a *constellation* of GPS hosts, and using them to
determine a computer's position.
In order to give the best results, a GPS constellation needs at least four computers. More than four GPS hosts per
constellation is redundant, but it does not cause problems.
## Prerequisites
You will need:
## Building a GPS constellation
<img alt="An example GPS constellation." src="../images/gps-constellation-example.png" class="big-image" />
- Four computers.
- Four Ender Modems. Normal Wireless Modems maybe be used, but the range of the GPS constellation will be severely
limited.
We are going to build our GPS constellation as shown in the image above. You will need 4 computers and either 4 wireless
modems or 4 ender modems. Try not to mix ender and wireless modems together as you might get some odd behavior when your
requesting computers are out of range.
Additionally, you will need another computer and a wireless modem, in order to test GPS works!
> [Ender modems vs wireless modems][!TIP]
> Ender modems have a very large range, which makes them very useful for setting up GPS hosts. If you do this then you
> will likely only need one GPS constellation for the whole dimension (such as the Overworld or Nether).
>
> If you do use wireless modems then you may find that you need multiple GPS constellations to cover your needs.
>
> A computer needs a wireless or ender modem and to be in range of a GPS constellation that is in the same dimension as
> it to use the GPS API. The reason for this is that ComputerCraft mimics real-life GPS by making use of the distance
> parameter of [modem messages][`modem_message`] and some maths.
## Picking an area
First, choose a place to build your GPS constellation. This should be a 10x10x10 cube, though you can make this smaller
if needed. The larger a constellation is, the more accurate it is over large distances, but even a 5x5x5 constellation
should serve a several thousand block radius.
Locate where you want to place your GPS constellation. You will need an area at least 6 blocks high, 6 blocks wide, and
6 blocks deep (6x6x6). If you are using wireless modems then you may want to build your constellation as high as you can
because high altitude boosts modem message range and thus the radius that your constellation covers.
Every computer must be loaded in order for other computers to use GPS, so it is recommended to build your GPS
constellation in a single chunk that will always be loaded. You may want to choose an area in an already chunk-loaded
part of your base, or in the [spawn chunks][spawn chunks]. You can use <kbd>F3+G</kbd> to view the chunk boundaries if
needed.
The GPS constellation will only work when it is in a loaded chunk. If you want your constellation to always be
accessible, you may want to permanently load the chunk using a vanilla or modded chunk loader. Make sure that your 6x6x6
area fits in a single chunk to reduce the number of chunks that need to be kept loaded.
[spawn chunks]: https://minecraft.wiki/w/Spawn_chunk "Spawn Chunk — Minecraft Wiki"
Let's get started building the constellation! Place your first computer in one of the corners of your 6x6x6. Remember
which computer this is as other computers need to be placed relative to it. Place the second computer 4 blocks above the
first. Go back to your first computer and place your third computer 5 blocks in front of your first computer, leaving 4
blocks of air between them. Finally for the fourth computer, go back to your first computer and place it 5 blocks right
of your first computer, leaving 4 blocks of air between them.
This is the example area we will be building our constellation in:
With all four computers placed within the 6x6x6, place one modem on top of each computer. You should have 4 modems and 4
computers all within your 6x6x6 where each modem is attached to a computer and each computer has a modem.
<img alt="An empty 10x10x10 area, with the axis marked with smooth stone." src="../images/gps-constellation-area.png" class="big-image" />
Currently your GPS constellation will not work, that's because each host is not aware that it's a GPS host. We will fix
this in the next section.
## Building the constellation
1. Place down your first computer in a corner of your area, and put a modem on top.
2. Head to the two adjacent corners of your area, place down another two computers and put a modem on top of each.
3. Pillar up above the first computer to the top of your cube, and place the final computer. Place a modem on the
computer.
You should now have something like this:
<img alt="The same area as before, but with a computer in each corner." src="../images/gps-constellation-built.png" class="big-image" />
## Configuring the constellation
Now that the structure of your constellation is built, we need to configure each host in it.
Go back to the first computer that you placed and create a startup file, by running `edit startup`.
1. Press <kbd>F3</kbd> to open Minecraft's debug screen.
2. Go back to the first computer and look at it. On the right of the screen about halfway down you should see an entry
labelled `Targeted Block`, the numbers correspond to the position of the block that you are looking at. Write these
numbers down.
3. Open the computer's UI, and run `edit startup.lua`.
4. Type the following code into the file, replacing `x`, `y`, and `z` with the coordinates you just wrote down.
Type the following code into the file:
```lua
shell.run("gps", "host", x, y, z)
```
```lua
shell.run("gps", "host", x, y, z)
```
5. Save the file, and then reboot the computer (hold <kbd>Ctrl+R</kbd> or run the `reboot` program) to run the startup
program.
Escape from the computer GUI and then press <kbd>F3</kbd> to open Minecraft's debug screen and then look at the computer
(without opening the GUI). On the right of the screen about halfway down you should see an entry labeled `Targeted
Block`, the numbers correspond to the position of the block that you are looking at. Replace `x` with the first number,
`y` with the second number, and `z` with the third number.
For example, if I had a computer at x = 59, y = 5, z = -150, then my <kbd>F3</kbd> debug screen entry would be `Target
Block: 59, 5, -150` and I would change my startup file to this `shell.run("gps", "host", 59, 5, -150)`.
To hide Minecraft's debug screen, press <kbd>F3</kbd> again.
Create similar startup files for the other computers in your constellation, making sure to input the each computer's own
coordinates.
> [Modem messages come from the computer's position, not the modem's][!WARNING]
> Wireless modems transmit from the block that they are attached to *not* the block space that they occupy, the
> coordinates that you input into your GPS host should be the position of the computer and not the position of the modem.
Repeat this process for the other three computers.
Congratulations, your constellation is now fully set up! You can test it by placing another computer close by, placing a
wireless modem on it, and running the `gps locate` program (or calling the [`gps.locate`] function).
> [Why use Minecraft's coordinates?][!INFO]
> CC doesn't care if you use Minecraft's coordinate system, so long as all of the GPS hosts with overlapping ranges use
> the same reference point (requesting computers will get confused if hosts have different reference points). However,
> using MC's coordinate system does provide a nice standard to adopt server-wide. It also is consistent with how command
> computers get their location, they use MC's command system to get their block which returns that in MC's coordinate
> system.

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 KiB

View File

@@ -16,7 +16,7 @@ CC: Tweaked is a mod for Minecraft which adds programmable computers, turtles an
much-beloved [ComputerCraft], it continues its legacy with improved performance and stability, along with a wealth of
new features.
CC: Tweaked can be installed from [CurseForge] or [Modrinth]. It runs on both [Minecraft Forge] and [Fabric].
CC: Tweaked can be installed from [Modrinth]. It runs on both [Minecraft Forge] and [Fabric].
## Features
Controlled using the [Lua programming language][lua], CC: Tweaked's computers provides all the tools you need to start
@@ -62,7 +62,6 @@ CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please
[github]: https://github.com/cc-tweaked/CC-Tweaked/ "CC: Tweaked on GitHub"
[bug]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose
[computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub"
[curseforge]: https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked from CurseForge"
[modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth"
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."

View File

@@ -0,0 +1,99 @@
---
module: [kind=reference] block_details
since: 1.64
changed: 1.76 Added block state.
changed: 1.117.0 Added map colour.
---
<!--
SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
# Block details
Several functions in CC: Tweaked, such as [`turtle.inspect`] and [`commands.getBlockInfo`] provide a way to get
information about a block in the world. This page details information about blocks that CC: Tweaked may return.
## Basic information
Block information will *always* contain:
- `name: string`: The namespaced ID for this block, e.g. `minecraft:dirt`. See [the Minecraft wiki][block ids] for a
list of vanilla block IDs.
- `state: { [string] = any}`: A table containing the block state of the block.
### Example
A fully hydrated block of farmland:
```lua {data-no-run=1}
{
name = "minecraft:farmland",
state = {
moisture = 7
}
}
```
An extended piston, facing upwards:
```lua {data-no-run=1}
{
name = "minecraft:piston",
state = {
facing = "up",
extended = true
}
}
```
## Block tags
The [tags][block tags] a block has.
- `tags: { [string] = boolean }`: The set of tags for this block. This is a mapping of tag name to `true`.
While the representation of tags is a little more complicated then a single list, this makes it very easy to check if a
block has a certain tag:
```lua
--- Check if the block in front of the turtle is a log.
local function is_log()
local ok, block = turtle.inspect()
return ok and block.tags["minecraft:logs"]
end
```
### Example
A fully hydrated block of farmland:
```lua {data-no-run=1}
{
name = "minecraft:farmland",
state = { ... },
tags = {
["minecraft:mineable/shovel"] = true,
}
}
```
## Map colour
The colour the block will appear on the map, if specified.
- `mapColour?: number`: The colour of the block, as an RGB hex value.
- `mapColor?: number`: The color of the block, as an RGB hex value.
The map colour is just returned as a plain number (e.g. `9923917` for farmland). It can either be displayed in hex with
[`string.format`], or converted to individual RGB values with [`colors.unpackRGB`].
### Example
A fully hydrated block of farmland:
```lua {data-no-run=1}
{
name = "minecraft:farmland",
state = { ... },
mapColour = 9923917,
mapColor = 9923917,
}
```
[block ids]: https://minecraft.wiki/w/Java_Edition_data_values#Blocks "Java Edition data values on the Minecraft Wiki"
[block tags]:https://minecraft.wiki/w/Block_tag_%28Java_Edition%29 "Block tags on the Minecraft Wiki"

View File

@@ -0,0 +1,267 @@
---
module: [kind=reference] item_details
since: 1.64
changed: 1.94.0 Add NBT hash, item tags, lore, enchantment and unbreakable flag.
changed: 1.100.9 Add item groups.
changed: 1.117.0 Added map colour and potion effects.
---
<!--
SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
# Item details
Several functions in CC: Tweaked, such as [`turtle.getItemDetail`] and [`inventory.getItemDetail`] provide a way to get
information about an item stack. This page details information about items that CC: Tweaked may return.
Some methods (such as [`inventory.list`] and [`turtle.getItemDetail`] without the `detailed` flag), will only return
the "Basic information" about the item.
## Basic information
Item information will *always* contain:
- `name: string`: The namespaced ID for this item, e.g. `minecraft:dirt`. See [the Minecraft wiki][item ids] for a
list of vanilla item IDs.
- `count: number`: The number of items in the stack.
- `nbt?: string`: A hash of the NBT in the stack. While this does not expose any information about the item's NBT, it
can be used as a way to compare items. If two items have the same `name` and `nbt`, then all other properties
(e.g. durability, enchantment) will be the same.
### Example
A stack of 32 Stripped Acacia Logs:
```lua {data-no-run=1}
{
name = "minecraft:stripped_acacia_log",
count = 32,
}
```
A turtle with an upgrade attached:
```lua {data-no-run=1}
{
name = "computercraft:turtle_normal",
count = 1,
nbt = "a33095c2eb17c10e12f2b970c4e1b2bb",
}
```
## Display information
Common information shown in the item's tooltip:
- `displayName: string`: The translated display name of the item. This uses the *server's* language. This will
typically be English on multi-player servers, and your current language on single player.
- `lore: { string... }`: Additional lore about this item, as a list of strings.
### Example
A stack of Stripped Acacia Logs:
```lua {data-no-run=1}
{
name = "minecraft:stripped_acacia_log",
count = 32,
displayName = "Stripped Acacia Log",
}
```
## Max count
The maximum number of items this item can stack to:
- `maxCount: number`: The max possible size of the item stack.
### Example
A stack of Stripped Acacia Logs:
```lua {data-no-run=1}
{
name = "minecraft:stripped_acacia_log",
count = 32,
maxCount = 64,
}
```
## Item tags
The [tags][item tags] an item has.
- `tags: { [string] = boolean }`: The set of tags for this item. This is a mapping of tag name to `true`.
While the representation of tags is a little more complicated then a single list, this makes it very easy to check if an
item has a certain tag:
```lua
--- Check if the item in the turtle's inventory is a log.
local function is_log(slot)
local ok, block = turtle.getItemDetail(slot, true)
return ok and block.tags["minecraft:logs"]
end
```
### Example
A stack of Stripped Acacia Logs:
```lua {data-no-run=1}
{
name = "minecraft:stripped_acacia_log",
count = 32,
tags = {
["minecraft:acacia_logs"] = true,
["minecraft:logs"] = true,
["minecraft:logs_that_burn"] = true,
}
}
```
## Item groups
The creative tabs this item appears on:
- `itemGroups: { { id = string, displayName = string }... }`: The item groups this item appears on. Each item group is
stored as a table, containing its id and display name.
> [Version differences][!INFO]
> This information is not available on Minecraft 1.19.3 to 1.20.3. This field is present, but empty on those versions.
### Example
A stack of Stripped Acacia Logs:
```lua {data-no-run=1}
{
name = "minecraft:stripped_acacia_log",
count = 32,
itemGroups = {
{
id = "minecraft:building_blocks",
displayName = "Building Blocks"
}
}
}
```
## Damage and durability
If this item can be damaged (e.g. a pickaxe), then its damage and durability will be available:
- `damage: number`: The amount of damage this item has taken.
- `maxDamage: number`: The maximum amount of damage this item has taken.
- `durability?: number`: If this item is damaged (i.e. the durability bar is visible), the percentage left on the
durability bar, between 0 and 1 (inclusive).
- `unbreakable?: boolean`: `true`, if the item is nubreakable
### Example
An unused diamond pickaxe:
```lua {data-no-run=1}
{
name = "minecraft:diamond_pickaxe",
count = 1,
damage = 0,
maxDamage = 1561,
}
```
A half-used wooden pickaxe:
```lua {data-no-run=1}
{
name = "minecraft:wooden_pickaxe",
count = 1,
damage = 21,
maxDamage = 59,
durability = 0.615,
}
```
## Enchantments
The enchantments this item has. This includes both tools and enchanted books.
- `enchantments: { table... }`: The enchantments this item has. Each enchantment is a table containing several
properties:
- `name: string`: The namespaced ID for this enchantment, e.g. `minecraft:efficiency`. See [the Minecraft
wiki][enchantment ids] for a list of vanilla enchantment IDs.
- `displayName: string`: The translated display name for this enchantment.
- `level: number`: The level for this enchantment.
### Example
A diamond pickaxe with Efficiency V:
```lua {data-no-run=1}
{
name = "minecraft:diamond_pickaxe",
count = 1,
enchantments = {
{
name = "minecraft:efficiency",
level = 5,
displayName = "Efficiency V",
}
}
}
```
## Potion effects
The effects this potion (or potion-embued item, such as a tipped arrow) has:
- `potionEffects: { table... }`: The effects this item has. Each potion effect is a table containing several
properties:
- `name: string`: The namespaced ID for this effect, e.g. `minecraft:regeneration`. See [the Minecraft wiki][effect
ids] for a list of vanilla effect IDs.
- `displayName: string`: The translated display name for this potion effect.
- `duration?: number`: The duration this effect will last for (if not instant), in seconds.
- `potency?: number`: The potency of this potion.
### Example
A basic Potion of Healing:
```lua {data-no-run=1}
{
name = "minecraft:potion",
displayName = "Potion of Healing",
potionEffects = {
{
name = "minecraft:instant_health",
displayName = "Instant Health",
},
},
}
```
An upgraded Potion of Regeneration:
```lua {data-no-run=1}
{
name = "minecraft:potion",
displayName = "Potion of Regeneration",
potionEffects = {
{
name = "minecraft:regeneration",
displayName = "Regeneration II",
duration = 22.5,
potency = 2,
},
},
}
```
## Map colour
The colour the item's block form will appear on the map, if specified.
- `mapColour?: number`: The colour of the block, as an RGB hex value.
- `mapColor?: number`: The color of the block, as an RGB hex value.
The map colour is just returned as a plain number (e.g. `9923917` for dirt). It can either be displayed in hex with
[`string.format`], or converted to individual RGB values with [`colors.unpackRGB`].
### Example
A stack of Stripped Acacia Logs:
```lua {data-no-run=1}
{
name = "minecraft:stripped_acacia_log",
count = 32,
mapColour = 14188339,
mapColor = 14188339,
}
```
[item ids]: https://minecraft.wiki/w/Java_Edition_data_values#Items "Java Edition data values on the Minecraft Wiki"
[item tags]:https://minecraft.wiki/w/Item_tag_%28Java_Edition%29 "Item tags on the Minecraft Wiki"
[effect ids]: https://minecraft.wiki/w/Java_Edition_data_values#Effects "Java Edition data values on the Minecraft Wiki"
[enchantment ids]: https://minecraft.wiki/w/Java_Edition_data_values#Enchantments "Java Edition data values on the Minecraft Wiki"

View File

@@ -117,10 +117,14 @@ function read(replaceChar, history, completeFn, default) end
-- Outside of Minecraft (for instance, in an emulator) [`_HOST`] will contain the
-- emulator's version instead.
--
-- If you need to check for the presence of a feature, it is usually better to
-- rely on feature detection, rather than comparing mod or Minecraft versions.
--
-- For example, `ComputerCraft 1.93.0 (Minecraft 1.15.2)`.
-- @usage Print the current computer's environment.
--
-- print(_HOST)
-- @see os.version
-- @since 1.76
_HOST = _HOST

View File

@@ -100,8 +100,12 @@ function sleep(time) end
-- This is defined by `bios.lua`. For the current version of CC:Tweaked, this
-- should return `CraftOS 1.9`.
--
-- If you need to check for the presence of a feature, it is usually better to
-- rely on feature detection, rather than comparing CraftOS versions.
--
-- @treturn string The current CraftOS version.
-- @usage os.version()
-- @see _G._HOST
function version() end
--[[- Run the program at the given path with the specified environment and

View File

@@ -7,8 +7,11 @@ The turtle's inventory should set up like a crafting grid. For instance, to
craft sticks, slots 1 and 5 should contain planks. _All_ other slots should be
empty, including those outside the crafting "grid".
`turtle.craft(0)` can be used to check whether the turtle contains a valid
recipe, without actually crafting it.
@tparam[opt=64] number limit The maximum number of crafting steps to run.
@throws When limit is less than 1 or greater than 64.
@throws When limit is less than 0 or greater than 64.
@treturn[1] true If crafting succeeds.
@treturn[2] false If crafting fails.
@treturn string A string describing why crafting failed.

View File

@@ -8,9 +8,11 @@ org.gradle.parallel=true
kotlin.stdlib.default.dependency=false
kotlin.jvm.target.validation.mode=error
neogradle.subsystems.conventions.runs.enabled=false
# Mod properties
isUnstable=false
modVersion=1.113.1
isUnstable=true
modVersion=1.117.1
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.20.1
mcVersion=26.1.2

View File

@@ -1,2 +1,2 @@
#This file is generated by updateDaemonJvm
toolchainVersion=17
toolchainVersion=25

View File

@@ -6,77 +6,69 @@
# Minecraft
# MC version is specified in gradle.properties, as we need that in settings.gradle.
# Remember to update corresponding versions in fabric.mod.json/mods.toml
fabric-api = "0.86.1+1.20.1"
fabric-loader = "0.14.21"
forge = "47.1.0"
forgeSpi = "7.0.1"
# Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml
fabric-api = "0.145.2+26.1.1"
fabric-loader = "0.18.4"
neoForge = "26.1.2.0-beta"
neoMergeTool = "2.0.0"
mixin = "0.8.5"
parchment = "2023.08.20"
parchmentMc = "1.20.1"
yarn = "1.20.1+build.10"
# Core dependencies (these versions are tied to the version Minecraft uses)
fastutil = "8.5.9"
guava = "31.1-jre"
netty = "4.1.82.Final"
slf4j = "2.0.1"
fastutil = "8.5.18"
guava = "33.5.0-jre"
netty = "4.2.7.Final"
slf4j = "2.0.17"
# Core dependencies (independent of Minecraft)
asm = "9.6"
asm = "9.9.1"
autoService = "1.1.1"
checkerFramework = "3.42.0"
cobalt = "0.9.4"
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2"
jzlib = "1.1.3"
kotlin = "1.9.21"
kotlin-coroutines = "1.7.3"
nightConfig = "3.6.7"
checkerFramework = "3.51.1"
cobalt = { strictly = "0.9.7" }
commonsCli = "1.10.0"
jetbrainsAnnotations = "26.0.2-1"
jspecify = "1.0.0"
kotlin = "2.3.0"
kotlin-coroutines = "1.10.2"
nightConfig = "3.8.3"
# Minecraft mods
emi = "1.0.8+1.20.1"
fabricPermissions = "0.3.20230723"
iris = "1.6.4+1.20"
jei = "15.2.0.22"
modmenu = "7.1.0"
moreRed = "4.0.0.4"
oculus = "1.2.5"
rei = "12.0.626"
rubidium = "0.6.1"
sodium = "mc1.20-0.4.10"
create-forge = "0.5.1.f-33"
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
fabricPermissions = "0.3.3"
iris-fabric = "1.9.1+1.21.7-fabric"
iris-forge = "1.9.1+1.21.7-neoforge"
jei = "29.3.0.22"
modmenu = "16.0.0-rc.1"
rei = "18.0.800"
sodium-fabric = "mc1.21.11-0.8.0-fabric"
sodium-forge = "mc1.21.11-0.8.0-neoforge"
mixinExtra = "0.3.5"
create-forge = "6.0.0-6"
create-fabric = "6.0.7.0+mc1.20.1-build.1716"
# Testing
hamcrest = "2.2"
jqwik = "1.8.2"
junit = "5.10.1"
hamcrest = "3.0"
jqwik = "1.9.3"
junit = "6.0.1"
junitPlatform = "6.0.1"
jmh = "1.37"
# Build tools
cctJavadoc = "1.8.2"
checkstyle = "10.14.1"
curseForgeGradle = "1.0.14"
errorProne-core = "2.27.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.7.1"
forgeGradle = "6.0.21"
cctJavadoc = "1.9.0"
checkstyle = "12.1.1"
errorProne-core = "2.45.0"
errorProne-plugin = "4.3.0"
fabric-loom = "1.15.4"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-73-g43ee16c"
librarian = "1.+"
lwjgl = "3.3.3"
minotaur = "2.+"
nullAway = "0.10.25"
shadow = "8.3.1"
spotless = "6.23.3"
taskTree = "2.1.1"
teavm = "0.11.0-SQUID.1"
vanillaExtract = "0.1.3"
versionCatalogUpdate = "0.8.1"
gradleVersions = "0.53.0"
ideaExt = "1.3"
illuaminate = "0.1.0-83-g1131f68"
lwjgl = "3.3.6"
minotaur = "2.8.7"
modDevGradle = "2.0.140"
nullAway = "0.12.14"
spotless = "8.0.0"
teavm = "0.14.0-SQUID.1"
vanillaExtract = "0.3.1"
versionCatalogUpdate = "1.0.1"
[libraries]
# Normal dependencies
@@ -87,11 +79,10 @@ checkerFramework = { module = "org.checkerframework:checker-qual", version.ref =
cobalt = { module = "cc.tweaked:cobalt", version.ref = "cobalt" }
commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" }
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
neoMergeTool = { module = "net.neoforged:mergetool", version.ref = "neoMergeTool" }
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" }
jzlib = { module = "com.jcraft:jzlib", version.ref = "jzlib" }
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
@@ -105,25 +96,24 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
# Minecraft mods
create-fabric = { module = "com.simibubi.create:create-fabric-1.20.1", version.ref = "create-fabric" }
create-forge = { module = "com.simibubi.create:create-1.20.1", version.ref = "create-forge" }
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
create-forge = { module = "com.simibubi.create:create-1.21.1", version.ref = "create-forge" }
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
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" }
iris-fabric = { module = "maven.modrinth:iris", version.ref = "iris-fabric" }
iris-forge = { module = "maven.modrinth:iris", version.ref = "iris-forge" }
jei-api = { module = "mezz.jei:jei-26.1.1-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-26.1.1-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-26.1.1-neoforge", version.ref = "jei" }
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
mixinExtra = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixinExtra" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
moreRed = { module = "commoble.morered:morered-1.20.1", version.ref = "moreRed" }
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
rei-api = { module = "me.shedaniel:RoughlyEnoughItems-api", version.ref = "rei" }
rei-builtin = { module = "me.shedaniel:RoughlyEnoughItems-default-plugin", version.ref = "rei" }
rei-fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" }
rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
sodium-fabric = { module = "maven.modrinth:sodium", version.ref = "sodium.fabric" }
sodium-forge = { module = "maven.modrinth:sodium", version.ref = "sodium.forge" }
# Testing
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
@@ -132,6 +122,7 @@ jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junitPlatform" }
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
jmh = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
jmh-processor = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
@@ -145,19 +136,17 @@ lwjgl-glfw = { module = "org.lwjgl:lwjgl-glfw" }
# Build tools
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
curseForgeGradle = { module = "net.darkhax.curseforgegradle:CurseForgeGradle", version.ref = "curseForgeGradle" }
errorProne-annotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "errorProne-core" }
errorProne-api = { module = "com.google.errorprone:error_prone_check_api", version.ref = "errorProne-core" }
errorProne-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorProne-core" }
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
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" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
modDevGradle = { module = "net.neoforged:moddev-gradle", version.ref = "modDevGradle" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
teavm-core = { module = "org.teavm:teavm-core", version.ref = "teavm" }
@@ -169,32 +158,27 @@ teavm-metaprogramming-impl = { module = "org.teavm:teavm-metaprogramming-impl",
teavm-platform = { module = "org.teavm:teavm-platform", version.ref = "teavm" }
teavm-tooling = { module = "org.teavm:teavm-tooling", version.ref = "teavm" }
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" }
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
[bundles]
annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
annotations = ["checkerFramework", "jetbrainsAnnotations", "jspecify"]
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
# Minecraft
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
externalMods-forge-runtime = ["jei-forge"]
externalMods-fabric-compile = ["fabricPermissions", "iris", "jei-api", "rei-api", "rei-builtin"]
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
externalMods-common = ["iris-forge", "jei-api", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["iris-forge", "jei-api"]
externalMods-forge-runtime = []
externalMods-fabric-compile = ["fabricPermissions", "iris-fabric", "jei-api", "rei-api", "rei-builtin"]
externalMods-fabric-runtime = []
# Testing
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]
testRuntime = ["junit-jupiter-engine", "junit-platform-launcher", "jqwik-engine"]
# Build tools
teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"]

Binary file not shown.

View File

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

12
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -115,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -173,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -206,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_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" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

3
gradlew.bat vendored
View File

@@ -70,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

1765
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,14 +9,15 @@
"@squid-dev/cc-web-term": "^2.0.0",
"preact": "^10.5.5",
"setimmediate": "^1.0.5",
"tslib": "^2.0.3"
"tslib": "^2.0.3",
"wasm-feature-detect": "^1.8.0"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-typescript": "^11.0.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-typescript": "^12.0.0",
"@rollup/plugin-url": "^8.0.1",
"@swc/core": "^1.3.92",
"@types/node": "^20.8.3",
"@types/node": "^25.0.0",
"lightningcss": "^1.22.0",
"preact-render-to-string": "^6.2.1",
"rehype": "^13.0.0",

View File

@@ -8,6 +8,8 @@ plugins {
id("cc-tweaked.vanilla")
}
val mcVersion: String by extra
java {
withJavadocJar()
}
@@ -16,9 +18,70 @@ dependencies {
api(project(":core-api"))
}
val javadocOverview by tasks.registering(Copy::class) {
from("src/overview.html")
into(layout.buildDirectory.dir(name))
expand(
mapOf(
"mcVersion" to mcVersion,
"modVersion" to version,
),
)
}
tasks.javadoc {
title = "CC: Tweaked $version for Minecraft $mcVersion"
include("dan200/computercraft/api/**/*.java")
options {
(this as StandardJavadocDocletOptions)
inputs.files(javadocOverview)
overview(javadocOverview.get().destinationDir.resolve("overview.html").absolutePath)
groups = mapOf(
"Common" to listOf(
"dan200.computercraft.api",
"dan200.computercraft.api.lua",
"dan200.computercraft.api.peripheral",
),
"Upgrades" to listOf(
"dan200.computercraft.api.client.turtle",
"dan200.computercraft.api.pocket",
"dan200.computercraft.api.turtle",
"dan200.computercraft.api.upgrades",
),
)
addBooleanOption("-allow-script-in-comments", true)
bottom(
"""
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/components/prism-core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
<link href=" https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css " rel="stylesheet">
""".trimIndent(),
)
val snippetSources = listOf(":common", ":fabric", ":forge").flatMap {
project(it).sourceSets["examples"].allSource.sourceDirectories
}
inputs.files(snippetSources)
addPathOption("-snippet-path").value = snippetSources
}
// Include the core-api in our javadoc export. This is wrong, but it means we can export a single javadoc dump.
source(project(":core-api").sourceSets.main.map { it.allJava })
options {
this as StandardJavadocDocletOptions
addBooleanOption("-allow-script-in-comments", true)
bottom(
"""
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/components/prism-core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
<link href=" https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css " rel="stylesheet">
""".trimIndent(),
)
}
}

View File

@@ -1,43 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
/**
* The public API for client-only code.
*
* @see dan200.computercraft.api.ComputerCraftAPI The main API
*/
public final class ComputerCraftAPIClient {
private ComputerCraftAPIClient() {
}
/**
* Register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
* <p>
* This may be called at any point after registry creation, though it is recommended to call it within your client
* setup step.
*
* @param serialiser The turtle upgrade serialiser.
* @param modeller The upgrade modeller.
* @param <T> The type of the turtle upgrade.
* @deprecated This method can lead to confusing load behaviour on Forge. Use
* {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} on Fabric, or
* {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} on Forge.
*/
@Deprecated(forRemoval = true)
public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
// TODO(1.20.4): Remove this
getInstance().registerTurtleUpgradeModeller(serialiser, modeller);
}
private static ComputerCraftAPIClientService getInstance() {
return ComputerCraftAPIClientService.get();
}
}

View File

@@ -0,0 +1,196 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client;
import com.google.common.base.Suppliers;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.QuadInstance;
import com.mojang.blaze3d.vertex.SheetedDecalTextureGenerator;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.block.dispatch.BlockModelRotation;
import net.minecraft.client.renderer.feature.ModelFeatureRenderer;
import net.minecraft.client.renderer.item.CuboidItemModelWrapper;
import net.minecraft.client.renderer.item.ItemModel;
import net.minecraft.client.renderer.item.ItemModelResolver;
import net.minecraft.client.renderer.item.ItemStackRenderState;
import net.minecraft.client.renderer.rendertype.RenderType;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ResolvedModel;
import net.minecraft.client.resources.model.geometry.BakedQuad;
import net.minecraft.client.resources.model.sprite.Material;
import net.minecraft.resources.Identifier;
import net.minecraft.world.entity.ItemOwner;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import org.joml.Vector3fc;
import org.jspecify.annotations.Nullable;
import java.util.List;
import java.util.function.Supplier;
/**
* A standalone model.
* <p>
* This is very similar to vanilla's {@link CuboidItemModelWrapper}, but suitable for use in both {@link ItemModel}s
* and block models. This is primarily intended for use with {@link TurtleUpgradeModel}s.
*/
public final class StandaloneModel {
private final List<BakedQuad> quads;
private final boolean useBlockLight;
private final Material.Baked particleIcon;
private final RenderType renderType;
private final Supplier<Vector3fc[]> extents;
/**
* Construct a new {@link StandaloneModel}.
*
* @param quads The list of quads which form this model.
* @param usesBlockLight Whether this uses block lighting. See {@link ItemStackRenderState.LayerRenderState#setUsesBlockLight(boolean)}.
* @param particleIcon The sprite for the model's particles. See {@link ItemStackRenderState.LayerRenderState#setParticleMaterial(Material.Baked)}.
* @param renderType The render type for this model.
*/
public StandaloneModel(List<BakedQuad> quads, boolean usesBlockLight, Material.Baked particleIcon, RenderType renderType) {
this.quads = quads;
this.useBlockLight = usesBlockLight;
this.particleIcon = particleIcon;
this.renderType = renderType;
this.extents = Suppliers.memoize(() -> CuboidItemModelWrapper.computeExtents(quads));
}
/**
* Load a model from a {@link ModelBaker} and bake it.
*
* @param model The model id to load.
* @param baker The model baker.
* @return The baked {@link StandaloneModel}.
*/
public static StandaloneModel of(Identifier model, ModelBaker baker) {
return of(baker.getModel(model), baker);
}
/**
* Bake a {@link ResolvedModel} into a {@link StandaloneModel}.
*
* @param model The resolved model.
* @param baker The model baker.
* @return The baked {@link StandaloneModel}.
*/
public static StandaloneModel of(ResolvedModel model, ModelBaker baker) {
return baker.compute(new CacheKey(model));
}
private record CacheKey(ResolvedModel model) implements ModelBaker.SharedOperationKey<StandaloneModel> {
@Override
public StandaloneModel compute(ModelBaker baker) {
return ofUncached(model(), baker);
}
@Override
public boolean equals(Object other) {
return other instanceof CacheKey(var otherModel) && model() == otherModel;
}
@Override
public int hashCode() {
return System.identityHashCode(model());
}
}
private static StandaloneModel ofUncached(ResolvedModel model, ModelBaker baker) {
var slots = model.getTopTextureSlots();
var quads = model.bakeTopGeometry(slots, baker, BlockModelRotation.IDENTITY).getAll();
return new StandaloneModel(
quads,
model.getTopGuiLight().lightLikeBlock(),
model.resolveParticleMaterial(slots, baker),
detectRenderType(quads)
);
}
private static RenderType detectRenderType(List<BakedQuad> list) {
if (list.isEmpty()) return Sheets.translucentItemSheet();
var atlas = list.getFirst().materialInfo().sprite().atlasLocation();
var mismatchedAtlas = list.stream()
.map(x -> x.materialInfo().sprite().atlasLocation())
.filter(x -> !x.equals(atlas)).findFirst().orElse(null);
if (mismatchedAtlas != null) {
throw new IllegalStateException("Multiple atlases used in model, expected " + atlas + ", but also got " + mismatchedAtlas);
}
var hasTranslucent = list.stream().anyMatch(x -> x.materialInfo().itemRenderType().hasBlending());
if (atlas.equals(TextureAtlas.LOCATION_ITEMS)) {
return hasTranslucent ? Sheets.translucentItemSheet() : Sheets.cutoutItemSheet();
} else if (atlas.equals(TextureAtlas.LOCATION_BLOCKS)) {
return hasTranslucent ? Sheets.translucentBlockItemSheet() : Sheets.cutoutBlockItemSheet();
} else {
throw new IllegalArgumentException("Atlas " + atlas + " can't be used for models");
}
}
/**
* Set up an {@link ItemStackRenderState.LayerRenderState} to render this model.
*
* @param layer The layer to set up.
* @see ItemModel#update(ItemStackRenderState, ItemStack, ItemModelResolver, ItemDisplayContext, ClientLevel, ItemOwner, int)
*/
public void setupItemLayer(ItemStackRenderState.LayerRenderState layer) {
layer.setExtents(extents);
layer.setUsesBlockLight(useBlockLight);
layer.setParticleMaterial(particleIcon);
layer.prepareQuadList().addAll(quads);
}
/**
* Render the model directly.
*
* @param transform The current pose stack transformations.
* @param collector The node collector to render to.
* @param light The current light texture coordinate.
* @param overlay The current overlay texture coordinate.
*/
public void submit(PoseStack transform, SubmitNodeCollector collector, int light, int overlay) {
submit(transform, collector, light, overlay, -1, null);
}
/**
* Render the model directly.
*
* @param transform The current pose stack transformations.
* @param collector The node collector to render to.
* @param light The current light texture coordinate.
* @param overlay The current overlay texture coordinate.
* @param tintColour The tint for this model.
* @param crumblingOverlay The current breaking progress.
*/
public void submit(PoseStack transform, SubmitNodeCollector collector, int light, int overlay, int tintColour, ModelFeatureRenderer.@Nullable CrumblingOverlay crumblingOverlay) {
collector.submitCustomGeometry(transform, renderType, (pose, buffer) -> render(pose, buffer, tintColour, light, overlay));
if (crumblingOverlay != null && renderType.affectsCrumbling()) {
// FIXME: We need a custom hook here, which renders to crumblingBufferSource. Currently the DESTROY_TYPES
// buffer gets flushed before the main model gets rendered.
collector.submitCustomGeometry(transform, ModelBakery.DESTROY_TYPES.get(crumblingOverlay.progress()), (pose, buffer) ->
render(pose, new SheetedDecalTextureGenerator(buffer, crumblingOverlay.cameraPose(), 1.0f), -1, light, overlay)
);
}
}
private void render(PoseStack.Pose pose, VertexConsumer buffer, int tintColour, int light, int overlay) {
var instance = new QuadInstance();
instance.setLightCoords(light);
instance.setOverlayCoords(overlay);
for (var quad : quads) {
instance.setColor(quad.materialInfo().isTinted() ? tintColour : -1);
buffer.putBakedQuad(pose, quad, instance);
}
}
}

View File

@@ -1,57 +0,0 @@
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client;
import com.mojang.math.Transformation;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import java.util.Objects;
/**
* A model to render, combined with a transformation matrix to apply.
*/
public final class TransformedModel {
private final BakedModel model;
private final Transformation matrix;
public TransformedModel(BakedModel model, Transformation matrix) {
this.model = Objects.requireNonNull(model);
this.matrix = Objects.requireNonNull(matrix);
}
public TransformedModel(BakedModel model) {
this.model = Objects.requireNonNull(model);
matrix = Transformation.identity();
}
public static TransformedModel of(ModelResourceLocation location) {
var modelManager = Minecraft.getInstance().getModelManager();
return new TransformedModel(modelManager.getModel(location));
}
public static TransformedModel of(ResourceLocation location) {
var modelManager = Minecraft.getInstance().getModelManager();
return new TransformedModel(ClientPlatformHelper.get().getModel(modelManager, location));
}
public static TransformedModel of(ItemStack item, Transformation transform) {
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(item);
return new TransformedModel(model, transform);
}
public BakedModel getModel() {
return model;
}
public Transformation getMatrix() {
return matrix;
}
}

View File

@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client.turtle;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.StandaloneModel;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import net.minecraft.client.renderer.item.CuboidItemModelWrapper;
import net.minecraft.client.renderer.item.ItemModelResolver;
import net.minecraft.client.renderer.item.ItemStackRenderState;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.cuboid.ItemTransform;
import net.minecraft.resources.Identifier;
/**
* A {@link TurtleUpgradeModel} that renders a basic model.
* <p>
* This is the {@link TurtleUpgradeModel} equivalent of {@link CuboidItemModelWrapper}.
*/
public final class BasicUpgradeModel implements TurtleUpgradeModel {
public static final Identifier ID = Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "sided");
public static final MapCodec<? extends TurtleUpgradeModel.Unbaked> CODEC = RecordCodecBuilder.<Unbaked>mapCodec(instance -> instance.group(
Identifier.CODEC.fieldOf("left").forGetter(Unbaked::left),
Identifier.CODEC.fieldOf("right").forGetter(Unbaked::right)
).apply(instance, Unbaked::new));
private final StandaloneModel left;
private final StandaloneModel right;
private BasicUpgradeModel(StandaloneModel left, StandaloneModel right) {
this.left = left;
this.right = right;
}
/**
* Create an unbaked {@link BasicUpgradeModel}.
*
* @param left The model when equipped on the left.
* @param right The model when equipped on the right.
* @return The unbaked turtle upgrade model.
*/
public static TurtleUpgradeModel.Unbaked unbaked(Identifier left, Identifier right) {
return new Unbaked(left, right);
}
private StandaloneModel getModel(TurtleSide side) {
return switch (side) {
case LEFT -> left;
case RIGHT -> right;
};
}
@Override
public void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) {
renderer.appendModelIdentityElement(this);
renderer.appendModelIdentityElement(side);
renderer.appendModelIdentityElement(transform);
var layer = renderer.newLayer();
layer.setItemTransform(transform);
getModel(side).setupItemLayer(layer);
}
private record Unbaked(Identifier left, Identifier right) implements TurtleUpgradeModel.Unbaked {
@Override
public MapCodec<? extends TurtleUpgradeModel.Unbaked> type() {
return CODEC;
}
@Override
public TurtleUpgradeModel bake(ModelBaker baker) {
return new BasicUpgradeModel(StandaloneModel.of(left(), baker), StandaloneModel.of(right(), baker));
}
@Override
public void resolveDependencies(Resolver resolver) {
resolver.markDependency(left());
resolver.markDependency(right());
}
}
}

View File

@@ -0,0 +1,131 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client.turtle;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import com.mojang.math.Transformation;
import com.mojang.serialization.MapCodec;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.item.ItemModelResolver;
import net.minecraft.client.renderer.item.ItemStackRenderState;
import net.minecraft.client.renderer.item.TrackingItemStackRenderState;
import net.minecraft.client.renderer.special.SpecialModelRenderer;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.cuboid.ItemTransform;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4f;
import org.joml.Vector3fc;
import org.jspecify.annotations.Nullable;
import java.util.function.Consumer;
/**
* A {@link TurtleUpgradeModel} that renders the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch)
* upgrade item}.
* <p>
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
* model type. It will not appear correct for 3D models with additional depth, such as blocks.
*/
public final class ItemUpgradeModel implements TurtleUpgradeModel {
private static final TurtleUpgradeModel.Unbaked UNBAKED = new Unbaked();
private static final TurtleUpgradeModel INSTANCE = new ItemUpgradeModel();
public static final Identifier ID = Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item");
public static final MapCodec<TurtleUpgradeModel.Unbaked> CODEC = MapCodec.unit(UNBAKED);
private static final TransformedRenderer LEFT = computeRenderer(TurtleSide.LEFT);
private static final TransformedRenderer RIGHT = computeRenderer(TurtleSide.RIGHT);
private ItemUpgradeModel() {
}
/**
* Get the unbaked {@link ItemUpgradeModel}.
*
* @return The unbaked item upgrade model.
*/
public static TurtleUpgradeModel.Unbaked unbaked() {
return UNBAKED;
}
@Override
public void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) {
renderer.appendModelIdentityElement(this);
var childState = new TrackingItemStackRenderState();
resolver.updateForTopItem(childState, upgrade.getUpgradeItem().create(), ItemDisplayContext.NONE, null, null, seed);
if (!childState.isEmpty()) {
renderer.appendModelIdentityElement(childState.getModelIdentity());
renderer.appendModelIdentityElement(transform);
var layer = renderer.newLayer();
layer.setItemTransform(transform);
layer.setupSpecialModel(getRenderer(side), childState);
}
}
private static final class Unbaked implements TurtleUpgradeModel.Unbaked {
@Override
public MapCodec<? extends TurtleUpgradeModel.Unbaked> type() {
return CODEC;
}
@Override
public TurtleUpgradeModel bake(ModelBaker baker) {
return INSTANCE;
}
@Override
public void resolveDependencies(Resolver resolver) {
}
}
private static TransformedRenderer computeRenderer(TurtleSide side) {
var pose = new Matrix4f();
pose.translate(0.5f, 0.5f, 0.5f);
pose.rotate(Axis.YN.rotationDegrees(90f));
pose.rotate(Axis.ZP.rotationDegrees(90f));
pose.translate(0.0f, 0.0f, side == TurtleSide.RIGHT ? -0.4065f : 0.4065f);
return new TransformedRenderer(new Transformation(pose));
}
private static TransformedRenderer getRenderer(TurtleSide side) {
return switch (side) {
case LEFT -> LEFT;
case RIGHT -> RIGHT;
};
}
private record TransformedRenderer(Transformation transform) implements SpecialModelRenderer<ItemStackRenderState> {
@Override
public void submit(
@Nullable ItemStackRenderState state, PoseStack poseStack, SubmitNodeCollector sink,
int light, int overlay, boolean foil, int outlineColour
) {
if (state == null) return;
poseStack.pushPose();
poseStack.mulPose(transform.getMatrix());
state.submit(poseStack, sink, light, overlay, outlineColour);
poseStack.popPose();
}
@Override
public void getExtents(Consumer<Vector3fc> set) {
}
@Override
public @Nullable ItemStackRenderState extractArgument(ItemStack itemStack) {
return null;
}
}
}

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client.turtle;
import com.mojang.serialization.MapCodec;
import net.minecraft.resources.Identifier;
/**
* A functional interface to register a {@link TurtleUpgradeModel}.
* <p>
* This interface is largely intended to be used from multi-loader code, to allow sharing registration code between
* multiple loaders.
*/
@FunctionalInterface
public interface RegisterTurtleUpgradeModel {
/**
* Register a {@link TurtleUpgradeModel}.
*
* @param id The id used for this type of upgrade model.
* @param model The codec used to read/decode an upgrade model.
*/
void register(Identifier id, MapCodec<? extends TurtleUpgradeModel.Unbaked> model);
}

View File

@@ -1,26 +0,0 @@
// 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.turtle.TurtleUpgradeSerialiser;
/**
* 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(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
}

View File

@@ -0,0 +1,201 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client.turtle;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.renderer.item.ItemModelResolver;
import net.minecraft.client.renderer.item.ItemStackRenderState;
import net.minecraft.client.renderer.item.SelectItemModel;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.cuboid.ItemTransform;
import net.minecraft.client.resources.model.cuboid.MissingCuboidModel;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.resources.Identifier;
import net.minecraft.util.Util;
import org.jspecify.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
/**
* A {@link TurtleUpgradeModel} which selects between different models based on the value of a component in
* {@linkplain UpgradeData#data() the upgrade's data}.
* <p>
* This is the {@link TurtleUpgradeModel} equivalent of {@link SelectItemModel}.
*
* @param <T> The type of value to switch on.
*/
public final class SelectUpgradeModel<T> implements TurtleUpgradeModel {
public static final Identifier ID = Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "select");
public static final MapCodec<? extends TurtleUpgradeModel.Unbaked> CODEC = RecordCodecBuilder.<Unbaked<?>>mapCodec(instance -> instance.group(
Cases.CODEC.forGetter(Unbaked::cases),
TurtleUpgradeModel.CODEC.optionalFieldOf("fallback").forGetter(Unbaked::fallback)
).apply(instance, Unbaked::new));
private final DataComponentType<T> component;
private final Map<T, TurtleUpgradeModel> cases;
private final TurtleUpgradeModel fallback;
private SelectUpgradeModel(DataComponentType<T> component, Map<T, TurtleUpgradeModel> cases, TurtleUpgradeModel fallback) {
this.component = component;
this.cases = cases;
this.fallback = fallback;
}
private TurtleUpgradeModel getModel(UpgradeData<ITurtleUpgrade> upgrade) {
var value = upgrade.get(component);
if (value == null) return fallback;
var model = cases.get(value);
return model != null ? model : fallback;
}
@Override
public void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed) {
getModel(upgrade).renderForItem(upgrade, side, renderer, resolver, transform, seed);
}
private record Unbaked<T>(
Cases<T> cases,
Optional<TurtleUpgradeModel.Unbaked> fallback
) implements TurtleUpgradeModel.Unbaked {
private static final TurtleUpgradeModel.Unbaked MISSING = BasicUpgradeModel.unbaked(MissingCuboidModel.LOCATION, MissingCuboidModel.LOCATION);
@Override
public MapCodec<? extends TurtleUpgradeModel.Unbaked> type() {
return CODEC;
}
@Override
public TurtleUpgradeModel bake(ModelBaker baker) {
Map<T, TurtleUpgradeModel> cases = new Object2ObjectOpenHashMap<>();
for (var condition : cases().cases()) {
var model = condition.getSecond().bake(baker);
for (var when : condition.getFirst()) cases.put(when, model);
}
return new SelectUpgradeModel<>(cases().component(), cases, fallback().orElse(MISSING).bake(baker));
}
@Override
public void resolveDependencies(Resolver resolver) {
cases().cases().forEach(x -> x.getSecond().resolveDependencies(resolver));
fallback().orElse(MISSING).resolveDependencies(resolver);
}
}
private record Cases<T>(DataComponentType<T> component, List<Pair<List<T>, TurtleUpgradeModel.Unbaked>> cases) {
private static final MapCodec<Cases<?>> CODEC = DataComponentType.CODEC.dispatchMap("property", Cases::component, Util.memoize(Cases::codec));
private static <T> MapCodec<Cases<T>> codec(DataComponentType<T> component) {
return RecordCodecBuilder.mapCodec(instance -> instance.group(
MapCodec.unit(component).forGetter(Cases::component),
caseCodec(component.codecOrThrow()).listOf().fieldOf("cases").validate(Cases::validate).forGetter(Cases::cases)
).apply(instance, Cases<T>::new));
}
private static <T> Codec<Pair<List<T>, TurtleUpgradeModel.Unbaked>> caseCodec(Codec<T> codec) {
return RecordCodecBuilder.create(instance -> instance.group(
codec.listOf().fieldOf("when").forGetter(Pair::getFirst),
TurtleUpgradeModel.CODEC.fieldOf("model").forGetter(Pair::getSecond)
).apply(instance, Pair::new));
}
private static <T> DataResult<List<Pair<List<T>, TurtleUpgradeModel.Unbaked>>> validate(List<Pair<List<T>, TurtleUpgradeModel.Unbaked>> cases) {
Multiset<T> multiset = HashMultiset.create();
for (var condition : cases) multiset.addAll(condition.getFirst());
if (multiset.isEmpty()) return DataResult.error(() -> "Empty cases");
if (multiset.size() != multiset.entrySet().size()) {
return DataResult.error(() -> "Duplicate case conditions: " + multiset.entrySet().stream()
.filter(x -> x.getCount() > 1)
.map(x -> Objects.toString(x.getElement()))
.collect(Collectors.joining(", ")));
}
return DataResult.success(cases);
}
}
/**
* Create a {@link SelectUpgradeModel} that selects a model based on a component.
*
* @param component The component to select.
* @param <T> The type the component stores.
* @return A {@link Builder}.
*/
public static <T> Builder<T> onComponent(DataComponentType<T> component) {
return new Builder<>(component);
}
/**
* A builder for constructing {@link SelectUpgradeModel}s.
*
* @param <T> The type of value to switch on.
*/
public static final class Builder<T> {
private final DataComponentType<T> component;
private final List<Pair<List<T>, TurtleUpgradeModel.Unbaked>> cases = new ArrayList<>();
private TurtleUpgradeModel.@Nullable Unbaked fallback;
private Builder(DataComponentType<T> component) {
this.component = component;
}
/**
* Add a case to our model.
*
* @param value The value for this case.
* @param model The model to use.
* @return {@code this}, for chaining.
*/
public Builder<T> when(T value, TurtleUpgradeModel.Unbaked model) {
return when(List.of(value), model);
}
/**
* Add a case to our model.
*
* @param values The value(s) for this case.
* @param model The model to use.
* @return {@code this}, for chaining.
*/
public Builder<T> when(List<T> values, TurtleUpgradeModel.Unbaked model) {
cases.add(Pair.of(values, model));
return this;
}
/**
* Add a fallback value, when no previous value matches or the component is not present.
*
* @param model The fallback model.
* @return {@code this}, for chaining.
*/
public Builder<T> fallback(TurtleUpgradeModel.Unbaked model) {
this.fallback = model;
return this;
}
/**
* Convert this builder into an unbaked model.
*
* @return The unbaked {@link SelectUpgradeModel}.
*/
public TurtleUpgradeModel.Unbaked create() {
return new Unbaked<>(new Cases<>(component, cases), Optional.ofNullable(fallback));
}
}
}

View File

@@ -0,0 +1,94 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client.turtle;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.item.ItemModel;
import net.minecraft.client.renderer.item.ItemModelResolver;
import net.minecraft.client.renderer.item.ItemStackRenderState;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ResolvableModel;
import net.minecraft.client.resources.model.cuboid.ItemTransform;
import net.minecraft.world.entity.ItemOwner;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4fc;
/**
* The model for a {@link ITurtleUpgrade}.
* <p>
* Turtle upgrade models are very similar to vanilla's {@link ItemModel}. Each upgrade's model is defined in JSON, and
* loaded from resource packs with other assets.
* <p>
* In most cases, upgrades can use one of the existing implementations of {@link TurtleUpgradeModel} (e.g.
* {@link BasicUpgradeModel} or {@link ItemUpgradeModel}), and do not need to subclass it. However, in the cases where
* a custom model is required, one should use
* {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModel} to register a
* model on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent} to register one
* on Forge.
* <p>
* See {@link ITurtleUpgrade} for a full example of registering turtle upgrades and their models.
*
* @see RegisterTurtleUpgradeModel For multi-loader registration support.
* @see ItemUpgradeModel A {@code TurtleUpgradeModel} which uses the upgrade's item.
* @see BasicUpgradeModel A {@code TurtleUpgradeModel} which renders a simple model.
*/
public interface TurtleUpgradeModel {
/**
* The directory from which turtle upgrade models are loaded. This may be used by data generators.
*/
String SOURCE = ComputerCraftAPI.MOD_ID + "/turtle_upgrade";
/**
* The codec used to read/write {@linkplain TurtleUpgradeModel.Unbaked unbaked upgrade models}.
*/
Codec<TurtleUpgradeModel.Unbaked> CODEC = Codec.lazyInitialized(() -> ComputerCraftAPIClientService.get().getTurtleUpgradeModelCodec());
/**
* Render this upgrade to an {@link ItemStackRenderState}. This is used for rendering the item form of the upgrade.
* <p>
* Like with {@link ItemModel}, implementations must be careful to call {@link ItemStackRenderState#appendModelIdentityElement}
* where appropriate.
*
* @param upgrade The upgrade being rendered.
* @param side Which side of the turtle (left or right) the upgrade is equipped on.
* @param renderer The render state to draw to.
* @param resolver The model resolver.
* @param transform The root model's transformation.
* @param seed The current model seed.
* @see ItemModel#update(ItemStackRenderState, ItemStack, ItemModelResolver, ItemDisplayContext, ClientLevel, ItemOwner, int)
*/
void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed);
/**
* An unbaked turtle model. Much like other unbaked models (e.g. {@link ItemModel.Unbaked}), this should resolve
* any dependencies and returned the fully-resolved model.
*/
interface Unbaked extends ResolvableModel {
/**
* The {@link MapCodec} used to read/write this unbaked model.
*
* @return The codec used to read/write this model.
* @see ItemModel.Unbaked#type()
*/
MapCodec<? extends Unbaked> type();
/**
* Bake this turtle model.
*
* @param baker The current model baker
* @return The baked upgrade model.
* @see ItemModel.Unbaked#bake(ItemModel.BakingContext, Matrix4fc)
*/
TurtleUpgradeModel bake(ModelBaker baker);
}
}

View File

@@ -1,122 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client.turtle;
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 net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
/**
* Provides models for a {@link ITurtleUpgrade}.
* <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 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.
*
* @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 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);
}
/**
* Get a list of models that this turtle modeller depends on.
* <p>
* Models included in this list will be loaded and baked alongside item and block models, and so may be referenced
* by {@link TransformedModel#of(ResourceLocation)}. You do not need to override this method if you will load models
* by other means.
*
* @return A list of models that this modeller depends on.
* @see UnbakedModel#getDependencies()
*/
default Collection<ResourceLocation> getDependencies() {
return List.of();
}
/**
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(CompoundTag)}
* upgrade item}.
* <p>
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
* model type. It will not appear correct for 3D models with additional depth, such as blocks.
*
* @param <T> The type of the turtle upgrade.
* @return The constructed modeller.
*/
@SuppressWarnings("unchecked")
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.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.
*
* @param left The model to use on the left.
* @param right The model to use on the right.
* @param <T> The type of the turtle upgrade.
* @return The constructed modeller.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
return new TurtleUpgradeModeller<>() {
@Override
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
}
@Override
public Collection<ResourceLocation> getDependencies() {
return List.of(left, right);
}
};
}
}

View File

@@ -1,55 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client.turtle;
import com.mojang.math.Transformation;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4f;
import javax.annotation.Nullable;
final class TurtleUpgradeModellers {
private static final Transformation leftTransform = getMatrixFor(-0.4065f);
private static final Transformation rightTransform = getMatrixFor(0.4065f);
private static Transformation getMatrixFor(float offset) {
var matrix = new Matrix4f();
matrix.set(new float[]{
0.0f, 0.0f, -1.0f, 1.0f + offset,
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f,
});
matrix.transpose();
return new Transformation(matrix);
}
static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller();
private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
return getModel(turtle == null ? upgrade.getCraftingItem() : upgrade.getUpgradeItem(turtle.getUpgradeNBTData(side)), side);
}
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
return getModel(upgrade.getUpgradeItem(data), side);
}
private TransformedModel getModel(ItemStack stack, TurtleSide side) {
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);
}
}
}

View File

@@ -1,55 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.impl.client;
import dan200.computercraft.impl.Services;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
@ApiStatus.Internal
public interface ClientPlatformHelper {
/**
* Equivalent to {@link ModelManager#getModel(ModelResourceLocation)} but for arbitrary {@link ResourceLocation}s.
*
* @param manager The model manager.
* @param location The model location.
* @return The baked model.
*/
BakedModel getModel(ModelManager manager, ResourceLocation location);
/**
* Wrap this model in a version which renders a foil/enchantment glint.
*
* @param model The model to wrap.
* @return The wrapped model.
* @see RenderType#glint()
*/
BakedModel createdFoiledModel(BakedModel model);
static ClientPlatformHelper get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;
}
final class Instance {
static final @Nullable ClientPlatformHelper INSTANCE;
static final @Nullable Throwable ERROR;
static {
var helper = Services.tryLoad(ClientPlatformHelper.class);
INSTANCE = helper.instance();
ERROR = helper.error();
}
private Instance() {
}
}
}

View File

@@ -1,20 +1,17 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.impl.client;
import dan200.computercraft.api.client.ComputerCraftAPIClient;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import com.mojang.serialization.Codec;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
import dan200.computercraft.impl.Services;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Backing interface for {@link ComputerCraftAPIClient}
* Bridge between implementation
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/
@@ -25,7 +22,7 @@ public interface ComputerCraftAPIClientService {
return instance == null ? Services.raise(ComputerCraftAPIClientService.class, Instance.ERROR) : instance;
}
<T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
Codec<TurtleUpgradeModel.Unbaked> getTurtleUpgradeModelCodec();
final class Instance {
static final @Nullable ComputerCraftAPIClientService INSTANCE;

View File

@@ -11,8 +11,6 @@ import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.IComputerSystem;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
@@ -26,8 +24,9 @@ import net.minecraft.core.Direction;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.function.Function;
/**
* The static entry point to the ComputerCraft API.
@@ -109,6 +108,16 @@ public final class ComputerCraftAPI {
return getInstance().createResourceMount(server, domain, subPath);
}
/**
* Registers a method source for generic peripherals.
*
* @param source A factory to create the method source.
* @see GenericSource
*/
public static void registerGenericSource(Function<MinecraftServer, GenericSource> source) {
getInstance().registerGenericSource(source);
}
/**
* Registers a method source for generic peripherals.
*
@@ -116,7 +125,7 @@ public final class ComputerCraftAPI {
* @see GenericSource
*/
public static void registerGenericSource(GenericSource source) {
getInstance().registerGenericSource(source);
getInstance().registerGenericSource(s -> source);
}
/**
@@ -143,16 +152,6 @@ public final class ComputerCraftAPI {
return getInstance().getBundledRedstoneOutput(world, pos, side);
}
/**
* Registers a media provider to provide {@link IMedia} implementations for Items.
*
* @param provider The media provider to register.
* @see MediaProvider
*/
public static void registerMediaProvider(MediaProvider provider) {
getInstance().registerMediaProvider(provider);
}
/**
* Attempt to get the game-wide wireless network.
*
@@ -171,16 +170,9 @@ public final class ComputerCraftAPI {
* using {@link ILuaAPI#getModuleName()} to expose this library as a module instead of as a global.
* <p>
* This may be used with {@link IComputerSystem#getComponent(ComputerComponent)} to only attach APIs to specific
* computers. For example, one can add an additional API just to turtles with the following code:
* computers. For example, one can add a new API just to turtles with the following code:
*
* <pre>{@code
* ComputerCraftAPI.registerAPIFactory(computer -> {
* // Read the turtle component.
* var turtle = computer.getComponent(ComputerComponents.TURTLE);
* // If present then add our API.
* return turtle == null ? null : new MyCustomTurtleApi(turtle);
* });
* }</pre>
* {@snippet class=com.example.examplemod.ExampleAPI region=register}
*
* @param factory The factory for your API subclass.
* @see ILuaAPIFactory

View File

@@ -5,11 +5,12 @@
package dan200.computercraft.api;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.Identifier;
import net.minecraft.tags.TagKey;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
@@ -26,6 +27,20 @@ public class ComputerCraftTags {
public static final TagKey<Item> WIRED_MODEM = make("wired_modem");
public static final TagKey<Item> MONITOR = make("monitor");
/**
* Floppy disks. Both the read/write version, and treasure disks.
*
* @since 1.116.0
*/
public static final TagKey<Item> DISKS = make("disks");
/**
* All pocket computers.
*
* @since 1.116.0
*/
public static final TagKey<Item> POCKET_COMPUTERS = make("pocket_computers");
/**
* Items which can be {@linkplain Item#use(Level, Player, InteractionHand) used} when calling
* {@code turtle.place()}.
@@ -35,8 +50,15 @@ public class ComputerCraftTags {
*/
public static final TagKey<Item> TURTLE_CAN_PLACE = make("turtle_can_place");
/**
* Items which can be dyed.
* <p>
* These items can be dyed with a recipe, and cleaned with a sponge.
*/
public static final TagKey<Item> DYEABLE = make("dyeable");
private static TagKey<Item> make(String name) {
return TagKey.create(Registries.ITEM, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
return TagKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
}
}
@@ -75,13 +97,13 @@ public class ComputerCraftTags {
public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable");
/**
* Block which can be {@linkplain BlockState#use(Level, Player, InteractionHand, BlockHitResult) used} when
* calling {@code turtle.place()}.
* Block which can be {@linkplain BlockState#useItemOn(ItemStack, Level, Player, InteractionHand, BlockHitResult) used}
* when calling {@code turtle.place()}.
*/
public static final TagKey<Block> TURTLE_CAN_USE = make("turtle_can_use");
private static TagKey<Block> make(String name) {
return TagKey.create(Registries.BLOCK, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
return TagKey.create(Registries.BLOCK, Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
}
}
}

View File

@@ -5,6 +5,8 @@
package dan200.computercraft.api.component;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.permissions.LevelBasedPermissionSet;
import net.minecraft.server.permissions.PermissionSet;
import org.jetbrains.annotations.ApiStatus;
/**
@@ -13,12 +15,12 @@ import org.jetbrains.annotations.ApiStatus;
@ApiStatus.NonExtendable
public interface AdminComputer {
/**
* The permission level that this computer can operate at.
* The permissions that this computer has.
*
* @return The permission level for this computer.
* @see CommandSourceStack#hasPermission(int)
* @return The permissions for this computer.
* @see CommandSourceStack#permissions()
*/
default int permissionLevel() {
return 2;
default PermissionSet permissions() {
return LevelBasedPermissionSet.GAMEMASTER;
}
}

View File

@@ -6,6 +6,7 @@ package dan200.computercraft.api.component;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.pocket.IPocketAccess;
import dan200.computercraft.api.pocket.PocketComputer;
import dan200.computercraft.api.turtle.ITurtleAccess;
/**
@@ -20,7 +21,7 @@ public class ComputerComponents {
/**
* The {@link IPocketAccess} associated with a pocket computer.
*/
public static final ComputerComponent<IPocketAccess> POCKET = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "pocket");
public static final ComputerComponent<PocketComputer> POCKET = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "pocket");
/**
* This component is only present on "command computers", and other computers with admin capabilities.

View File

@@ -4,10 +4,11 @@
package dan200.computercraft.api.detail;
import net.minecraft.core.HolderLookup;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -49,22 +50,23 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
* This method is always called on the server thread, so it is safe to interact with the world here, but you should
* take care to avoid long blocking operations as this will stall the server and other computers.
*
* @param data The full details to be returned for this item stack. New properties should be added to this map.
* @param stack The item stack to provide details for.
* @param item The item to provide details for.
* @param data The full details to be returned for this item stack. New properties should be added to this map.
* @param registries The current registries.
* @param stack The item stack to provide details for.
* @param item The item to provide details for.
*/
public abstract void provideDetails(Map<? super String, Object> data, ItemStack stack, T item);
public abstract void provideDetails(Map<? super String, Object> data, HolderLookup.Provider registries, ItemStack stack, T item);
@Override
public final void provideDetails(Map<? super String, Object> data, ItemStack stack) {
public final void provideDetails(Map<? super String, Object> data, HolderLookup.Provider registries, ItemStack stack) {
var item = stack.getItem();
if (!itemType.isInstance(item)) return;
if (namespace == null) {
provideDetails(data, stack, itemType.cast(item));
provideDetails(data, registries, stack, itemType.cast(item));
} else {
Map<? super String, Object> child = new HashMap<>();
provideDetails(child, stack, itemType.cast(item));
provideDetails(child, registries, stack, itemType.cast(item));
data.put(namespace, child);
}
}

View File

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

View File

@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.detail;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponentHolder;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* An item detail provider for a specific {@linkplain DataComponentType data component} on {@link ItemStack}s or
* other {@link DataComponentHolder}.
*
* @param <T> The type of the component's contents.
*/
public abstract class ComponentDetailProvider<T> implements DetailProvider<DataComponentHolder> {
private final DataComponentType<T> component;
private final @Nullable String namespace;
/**
* Create a new component detail provider. Details will be inserted into a new sub-map named as per {@code namespace}.
*
* @param component The data component to provide details for.
* @param namespace The namespace to use for this provider.
*/
public ComponentDetailProvider(@Nullable String namespace, DataComponentType<T> component) {
Objects.requireNonNull(component);
this.component = component;
this.namespace = namespace;
}
/**
* Create a new component detail provider. Details will be inserted directly into the results.
*
* @param component The data component to provide details for.
*/
public ComponentDetailProvider(DataComponentType<T> component) {
this(null, component);
}
/**
* Provide additional details for the given data component. This method is called by {@code turtle.getItemDetail()}.
* New properties should be added to the given {@link Map}, {@code data}.
* <p>
* This method is always called on the server thread, so it is safe to interact with the world here, but you should
* take care to avoid long blocking operations as this will stall the server and other computers.
*
* @param data The full details to be returned for this item stack. New properties should be added to this map.
* @param registries The current registries.
* @param component The component to provide details for.
*/
public abstract void provideComponentDetails(Map<? super String, Object> data, HolderLookup.Provider registries, T component);
@Override
public final void provideDetails(Map<? super String, Object> data, HolderLookup.Provider registries, DataComponentHolder holder) {
var value = holder.get(component);
if (value == null) return;
if (namespace == null) {
provideComponentDetails(data, registries, value);
} else {
Map<? super String, Object> child = new HashMap<>();
provideComponentDetails(child, registries, value);
data.put(namespace, child);
}
}
}

View File

@@ -4,6 +4,8 @@
package dan200.computercraft.api.detail;
import net.minecraft.core.HolderLookup;
import java.util.Map;
/**
@@ -14,6 +16,7 @@ import java.util.Map;
*
* @param <T> The type of object that this provider can provide details for.
* @see DetailRegistry
* @see dan200.computercraft.api.detail An overview of the detail system.
*/
@FunctionalInterface
public interface DetailProvider<T> {
@@ -25,8 +28,9 @@ public interface DetailProvider<T> {
* This method is always called on the server thread, so it is safe to interact with the world here, but you should
* take care to avoid long blocking operations as this will stall the server and other computers.
*
* @param data The full details to be returned. New properties should be added to this map.
* @param object The object to provide details for.
* @param registries The current registries.
* @param data The full details to be returned. New properties should be added to this map.
* @param object The object to provide details for.
*/
void provideDetails(Map<? super String, Object> data, T object);
void provideDetails(Map<? super String, Object> data, HolderLookup.Provider registries, T object);
}

View File

@@ -4,9 +4,11 @@
package dan200.computercraft.api.detail;
import net.minecraft.core.HolderLookup;
import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
import java.util.function.BiConsumer;
/**
* A registry which provides computer-visible detail about in-game objects such as blocks, items or fluids.
@@ -17,6 +19,7 @@ import java.util.Map;
* also in this package.
*
* @param <T> The type of object that this registry provides details for.
* @see dan200.computercraft.api.detail An overview of the detail system.
*/
@ApiStatus.NonExtendable
public interface DetailRegistry<T> {
@@ -28,24 +31,36 @@ public interface DetailRegistry<T> {
*/
void addProvider(DetailProvider<? super T> provider);
/**
* Registers a detail provider. This is a simpler form of {@link #addProvider(DetailProvider)}, for providers which
* do not require access to registries.
*
* @param provider The detail provider to register.
*/
default void addProvider(BiConsumer<Map<? super String, Object>, ? super T> provider) {
addProvider((m, r, d) -> provider.accept(m, d));
}
/**
* Compute basic details about an object. This is cheaper than computing all details operation, and so is suitable
* for when you need to compute the details for a large number of values.
* <p>
* This method <em>MAY</em> be thread safe: consult the instance's documentation for details.
*
* @param object The object to get details for.
* @param registries The current registries.
* @param object The object to get details for.
* @return The basic details.
*/
Map<String, Object> getBasicDetails(T object);
Map<String, Object> getBasicDetails(HolderLookup.Provider registries, T object);
/**
* Compute all details about an object, using {@link #getBasicDetails(Object)} and any registered providers.
* Compute all details about an object, using {@link #getBasicDetails(HolderLookup.Provider, Object)} and any registered providers.
* <p>
* This method is <em>NOT</em> thread safe. It should only be called from the computer thread.
*
* @param object The object to get details for.
* @param registries The current registries.
* @param object The object to get details for.
* @return The computed details.
*/
Map<String, Object> getDetails(T object);
Map<String, Object> getDetails(HolderLookup.Provider registries, T object);
}

View File

@@ -5,6 +5,7 @@
package dan200.computercraft.api.detail;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.core.HolderLookup;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
@@ -15,16 +16,19 @@ public class VanillaDetailRegistries {
/**
* Provides details for {@link ItemStack}s.
* <p>
* This instance's {@link DetailRegistry#getBasicDetails(Object)} is thread safe (assuming the stack is immutable)
* and may be called from the computer thread.
* This instance's {@link DetailRegistry#getBasicDetails(HolderLookup.Provider, Object)} is thread safe (assuming
* the stack is immutable) and may be called from the computer thread.
* <p>
* This does not have special handling for {@linkplain ItemStack#isEmpty() empty item stacks}, and so the returned
* details will be an empty stack of air. Callers should generally check for empty stacks before calling this.
*/
public static final DetailRegistry<ItemStack> ITEM_STACK = ComputerCraftAPIService.get().getItemStackDetailRegistry();
/**
* Provides details for {@link BlockReference}, a reference to a {@link Block} in the world.
* <p>
* This instance's {@link DetailRegistry#getBasicDetails(Object)} is thread safe and may be called from the computer
* thread.
* This instance's {@link DetailRegistry#getBasicDetails(HolderLookup.Provider, Object)} is thread safe and may be
* called from the computer thread.
*/
public static final DetailRegistry<BlockReference> BLOCK_IN_WORLD = ComputerCraftAPIService.get().getBlockInWorldDetailRegistry();
}

View File

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

View File

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

View File

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

View File

@@ -7,29 +7,32 @@ package dan200.computercraft.api.media;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import net.minecraft.world.item.JukeboxSong;
import org.jspecify.annotations.Nullable;
/**
* Represents an item that can be placed in a disk drive and used by a Computer.
* <p>
* Implement this interface on your {@link Item} class to allow it to be used in the drive. Alternatively, register
* a {@link MediaProvider}.
* Implement this interface on your {@link Item} class to allow it to be used in the drive, or register via
* {@code dan200.computercraft.api.media.MediaLookup} (Fabric) or {@code dan200.computercraft.api.media.MediaCapability}
* (NeoForge).
*/
public interface IMedia {
/**
* Get a string representing the label of this item. Will be called via {@code disk.getLabel()} in lua.
*
* @param stack The {@link ItemStack} to inspect.
* @param registries The currently loaded registries.
* @param stack The {@link ItemStack} to inspect.
* @return The label. ie: "Dan's Programs".
*/
@Nullable
String getLabel(ItemStack stack);
String getLabel(HolderLookup.Provider registries, ItemStack stack);
/**
* Set a string representing the label of this item. Will be called vi {@code disk.setLabel()} in lua.
@@ -43,26 +46,15 @@ public interface IMedia {
}
/**
* If this disk represents an item with audio (like a record), get the readable name of the audio track. ie:
* "Jonathan Coulton - Still Alive"
* If this disk represents an item with audio (like a record), get the corresponding {@link JukeboxSong}.
*
* @param stack The {@link ItemStack} to modify.
* @return The name, or null if this item does not represent an item with audio.
* @param registries The currently loaded registries.
* @param stack The {@link ItemStack} to query.
* @return The song, or null if this item does not represent an item with audio.
*/
@Nullable
default String getAudioTitle(ItemStack stack) {
return null;
}
/**
* If this disk represents an item with audio (like a record), get the resource name of the audio track to play.
*
* @param stack The {@link ItemStack} to modify.
* @return The name, or null if this item does not represent an item with audio.
*/
@Nullable
default SoundEvent getAudio(ItemStack stack) {
return null;
default Holder<JukeboxSong> getAudio(HolderLookup.Provider registries, ItemStack stack) {
return JukeboxSong.fromStack(stack).orElse(null);
}
/**

View File

@@ -1,27 +0,0 @@
// Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
//
// SPDX-License-Identifier: LicenseRef-CCPL
package dan200.computercraft.api.media;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
/**
* This interface is used to provide {@link IMedia} implementations for {@link ItemStack}.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(MediaProvider)
*/
@FunctionalInterface
public interface MediaProvider {
/**
* Produce an IMedia implementation from an ItemStack.
*
* @param stack The stack from which to extract the media information.
* @return An {@link IMedia} implementation, or {@code null} if the item is not something you wish to handle
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(MediaProvider)
*/
@Nullable
IMedia getMedia(ItemStack stack);
}

View File

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

View File

@@ -14,13 +14,13 @@ import dan200.computercraft.api.ComputerCraftAPI;
* as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
* for its lifespan.
* <p>
* Elements are generally tied to a block or tile entity in world. In such as case, one should provide the
* Elements are generally tied to a block or block entity in world. In such as case, one should provide the
* {@link WiredElement} capability for the appropriate sides.
*/
public interface WiredElement extends WiredSender {
/**
* Called when objects on the network change. This may occur when network nodes are added or removed, or when
* peripherals change.
* Called when peripherals on the network change. This may occur when network nodes are added or removed, or when
* peripherals are attached or detached from a modem.
*
* @param change The change which occurred.
* @see WiredNetworkChange

View File

@@ -1,92 +0,0 @@
// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
/**
* A wired network is composed of one of more {@link WiredNode}s, a set of connections between them, and a series
* of peripherals.
* <p>
* Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if
* there is some path between two nodes then they must be on the same network. {@link WiredNetwork} will automatically
* handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections
* change.
* <p>
* This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently,
* it is generally preferred to use the methods provided by {@link WiredNode}.
*
* @see WiredNode#getNetwork()
*/
@ApiStatus.NonExtendable
public interface WiredNetwork {
/**
* Create a connection between two nodes.
* <p>
* This should only be used on the server thread.
*
* @param left The first node to connect
* @param right The second node to connect
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @throws IllegalStateException If neither node is on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see WiredNode#connectTo(WiredNode)
* @see WiredNetwork#connect(WiredNode, WiredNode)
* @deprecated Use {@link WiredNode#connectTo(WiredNode)}
*/
@Deprecated
boolean connect(WiredNode left, WiredNode right);
/**
* Destroy a connection between this node and another.
* <p>
* This should only be used on the server thread.
*
* @param left The first node in the connection.
* @param right The second node in the connection.
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If either node is not on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see WiredNode#disconnectFrom(WiredNode)
* @see WiredNetwork#connect(WiredNode, WiredNode)
* @deprecated Use {@link WiredNode#disconnectFrom(WiredNode)}
*/
@Deprecated
boolean disconnect(WiredNode left, WiredNode right);
/**
* Sever all connections this node has, removing it from this network.
* <p>
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param node The node to remove
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see WiredNode#remove()
* @deprecated Use {@link WiredNode#remove()}
*/
@Deprecated
boolean remove(WiredNode node);
/**
* Update the peripherals a node provides.
* <p>
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param node The node to attach peripherals for.
* @param peripherals The new peripherals for this node.
* @throws IllegalArgumentException If the node is not in the network.
* @see WiredNode#updatePeripherals(Map)
* @deprecated Use {@link WiredNode#updatePeripherals(Map)}
*/
@Deprecated
void updatePeripherals(WiredNode node, Map<String, IPeripheral> peripherals);
}

View File

@@ -11,7 +11,7 @@ import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
/**
* Wired nodes act as a layer between {@link WiredElement}s and {@link WiredNetwork}s.
* A single node on a wired network.
* <p>
* Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These
* methods may be safely used on any thread.
@@ -32,18 +32,6 @@ public interface WiredNode extends PacketNetwork {
*/
WiredElement getElement();
/**
* The network this node is currently connected to. Note that this may change
* after any network operation, so it should not be cached.
* <p>
* This should only be used on the server thread.
*
* @return This node's network.
* @deprecated Use the connect/disconnect/remove methods on {@link WiredNode}.
*/
@Deprecated
WiredNetwork getNetwork();
/**
* Create a connection from this node to another.
* <p>

View File

@@ -8,10 +8,12 @@ import dan200.computercraft.api.network.PacketSender;
/**
* An object on a {@link WiredNetwork} capable of sending packets.
* An object on a wired network capable of sending packets.
* <p>
* Unlike a regular {@link PacketSender}, this must be associated with the node you are attempting to
* to send the packet from.
*
* @see WiredElement
*/
public interface WiredSender extends PacketSender {
/**

View File

@@ -4,9 +4,8 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStackTemplate;
/**
@@ -15,32 +14,25 @@ import net.minecraft.world.item.ItemStack;
* One does not have to use this, but it does provide a convenient template.
*/
public abstract class AbstractPocketUpgrade implements IPocketUpgrade {
private final ResourceLocation id;
private final String adjective;
private final ItemStack stack;
private final Component adjective;
private final ItemStackTemplate stack;
protected AbstractPocketUpgrade(ResourceLocation id, String adjective, ItemStack stack) {
this.id = id;
protected AbstractPocketUpgrade(Component adjective, ItemStackTemplate stack) {
this.adjective = adjective;
this.stack = stack;
}
protected AbstractPocketUpgrade(ResourceLocation id, ItemStack stack) {
this(id, UpgradeBase.getDefaultAdjective(id), stack);
protected AbstractPocketUpgrade(String adjective, ItemStackTemplate stack) {
this(Component.translatable(adjective), stack);
}
@Override
public final ResourceLocation getUpgradeID() {
return id;
}
@Override
public final String getUnlocalisedAdjective() {
public final Component getAdjective() {
return adjective;
}
@Override
public final ItemStack getCraftingItem() {
public final ItemStackTemplate getCraftingItem() {
return stack;
}
}

View File

@@ -4,67 +4,18 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.world.item.ItemStackTemplate;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import java.util.Map;
import org.jspecify.annotations.Nullable;
/**
* Wrapper class for pocket computers.
* Access to a pocket computer for {@linkplain IPocketUpgrade pocket upgrades}.
*/
@ApiStatus.NonExtendable
public interface IPocketAccess {
/**
* Get the level in which the pocket computer exists.
*
* @return The pocket computer's level.
*/
ServerLevel getLevel();
/**
* Get the position of the pocket computer.
*
* @return The pocket computer's position.
*/
Vec3 getPosition();
/**
* Gets the entity holding this item.
* <p>
* This must be called on the server thread.
*
* @return The holding entity, or {@code null} if none exists.
*/
@Nullable
Entity getEntity();
/**
* Get the colour of this pocket computer as a RGB number.
*
* @return The colour this pocket computer is. This will be a RGB colour between {@code 0x000000} and
* {@code 0xFFFFFF} or -1 if it has no colour.
* @see #setColour(int)
*/
int getColour();
/**
* Set the colour of the pocket computer to a RGB number.
*
* @param colour The colour this pocket computer should be changed to. This should be a RGB colour between
* {@code 0x000000} and {@code 0xFFFFFF} or -1 to reset to the default colour.
* @see #getColour()
*/
void setColour(int colour);
public interface IPocketAccess extends PocketComputer {
/**
* Get the colour of this pocket computer's light as a RGB number.
*
@@ -87,7 +38,7 @@ public interface IPocketAccess {
* Get the currently equipped upgrade.
*
* @return The currently equipped upgrade.
* @see #getUpgradeNBTData()
* @see #getUpgradeData()
* @see #setUpgrade(UpgradeData)
*/
@Nullable
@@ -96,7 +47,8 @@ public interface IPocketAccess {
/**
* Set the upgrade for this pocket computer, also updating the item stack.
* <p>
* Note this method is not thread safe - it must be called from the server thread.
* This method can only be called from the main server thread, when this computer is {@linkplain #isActive() is
* active}.
*
* @param upgrade The new upgrade to set it to, may be {@code null}.
* @see #getUpgrade()
@@ -109,19 +61,23 @@ public interface IPocketAccess {
* This is persisted between computer reboots and chunk loads.
*
* @return The upgrade's NBT.
* @see #updateUpgradeNBTData()
* @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see UpgradeBase#getUpgradeData(ItemStack)
* @see #setUpgradeData(DataComponentPatch)
* @see UpgradeBase#getUpgradeItem(DataComponentPatch)
* @see UpgradeBase#getUpgradeData(ItemStackTemplate)
* @see #getUpgrade()
*/
CompoundTag getUpgradeNBTData();
DataComponentPatch getUpgradeData();
/**
* Mark the upgrade-specific NBT as dirty.
* Update the upgrade-specific data.
* <p>
* This method can only be called from the main server thread, when this computer is {@linkplain #isActive() is
* active}.
*
* @see #getUpgradeNBTData()
* @param data The new upgrade data.
* @see #getUpgradeData()
*/
void updateUpgradeNBTData();
void setUpgradeData(DataComponentPatch data);
/**
* Remove the current peripheral and create a new one.
@@ -130,13 +86,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

@@ -4,25 +4,46 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import dan200.computercraft.api.upgrades.UpgradeType;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.core.Registry;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import org.jspecify.annotations.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 UpgradeType} instance, which are then registered in a Minecraft 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.
*
* @see PocketUpgradeSerialiser For how to register a pocket computer upgrade.
* the upgrade registered internally.
*/
public interface IPocketUpgrade extends UpgradeBase {
ResourceKey<Registry<IPocketUpgrade>> REGISTRY = ResourceKey.createRegistryKey(Identifier.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_upgrade"));
/**
* The registry key for pocket upgrade types.
*
* @return The registry key.
*/
static ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> typeRegistry() {
return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
}
/**
* Get the type of this upgrade.
*
* @return The type of this upgrade.
*/
@Override
UpgradeType<? extends IPocketUpgrade> getType();
/**
* Creates a peripheral for the pocket computer.
* <p>
@@ -50,7 +71,7 @@ public interface IPocketUpgrade extends UpgradeBase {
/**
* Called when the pocket computer is right clicked.
*
* @param world The world the computer is in.
* @param level The world the computer is in.
* @param access The access object for the pocket item stack.
* @param peripheral The peripheral for this upgrade.
* @return {@code true} to stop the GUI from opening, otherwise false. You should always provide some code path
@@ -58,7 +79,7 @@ public interface IPocketUpgrade extends UpgradeBase {
* access the GUI.
* @see #createPeripheral(IPocketAccess)
*/
default boolean onRightClick(Level world, IPocketAccess access, @Nullable IPeripheral peripheral) {
default boolean onRightClick(ServerLevel level, IPocketAccess access, @Nullable IPeripheral peripheral) {
return false;
}
}

View File

@@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.pocket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;
/**
* A pocket computer.
*
* @see IPocketAccess
* @see dan200.computercraft.api.component.ComputerComponents#POCKET
*/
@ApiStatus.NonExtendable
public interface PocketComputer {
/**
* Get the level in which the pocket computer exists.
*
* @return The pocket computer's level.
*/
ServerLevel getLevel();
/**
* Get the position of the pocket computer.
*
* @return The pocket computer's position.
*/
Vec3 getPosition();
/**
* Gets the entity holding this item.
* <p>
* This must be called on the server thread.
*
* @return The holding entity, or {@code null} if none exists.
*/
@Nullable
Entity getEntity();
/**
* Check whether this pocket computer is currently being held by a player, lectern, or other valid entity.
* <p>
* As pocket computers are backed by item stacks, you must check for validity before updating the computer.
* <p>
* This must be called on the server thread.
*
* @return Whether this computer is active.
*/
boolean isActive();
/**
* Get the colour of this pocket computer as an RGB number.
*
* <p>
* This method can only be called from the main server thread, when this computer is {@linkplain #isActive() is
* active}.
*
* @return The colour this pocket computer is. This will be a RGB colour between {@code 0x000000} and
* {@code 0xFFFFFF} or -1 if it has no colour.
* @see #setColour(int)
*/
int getColour();
/**
* Set the colour of the pocket computer to an RGB number.
* <p>
* This method can only be called from the main server thread, when this computer is {@linkplain #isActive() is
* active}.
*
* @param colour The colour this pocket computer should be changed to. This should be a RGB colour between
* {@code 0x000000} and {@code 0xFFFFFF} or -1 to reset to the default colour.
* @see #getColour()
*/
void setColour(int colour);
}

View File

@@ -1,26 +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.UpgradeDataProvider;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import java.util.function.Consumer;
/**
* A data provider to generate pocket computer upgrades.
* <p>
* This should be subclassed and registered to a {@link DataGenerator.PackGenerator}. Override the
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
* generate them.
*
* @see PocketUpgradeSerialiser
*/
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>> {
public PocketUpgradeDataProvider(PackOutput output) {
super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.registryId());
}
}

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);
}
}

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