1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-15 14:07:38 +00:00

Compare commits

..

215 Commits

Author SHA1 Message Date
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
5eb50ecb06 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-11-23 09:16:48 +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
0056709999 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-11-15 09:25:46 +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
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
1403 changed files with 23550 additions and 29465 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,6 +1,7 @@
name: Bug report
description: Report some misbehaviour in the mod
labels: [ bug ]
type: bug
body:
- type: dropdown
id: mc-version
@@ -10,7 +11,8 @@ body:
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
@@ -29,3 +31,5 @@ body:
Description of the bug. Please include the following:
- Logs: These will be located in the `logs/` directory of your Minecraft instance. This is always useful, even if it doesn't include errors, so please upload this!
- Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed.
![A gif of burning text reading "Upload your logs!!!"](https://tweaked.cc/images/logs.gif)

View File

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

View File

@@ -14,7 +14,7 @@ jobs:
- name: 📥 Set up Java
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 21
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:
@@ -54,18 +66,6 @@ jobs:
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
build-core:
strategy:
fail-fast: false
@@ -87,7 +87,7 @@ jobs:
- name: 📥 Set up Java
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 21
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: 21
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: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -27,7 +27,7 @@ repos:
exclude: "^(.*\\.(bat)|LICENSE)$"
- repo: https://github.com/fsfe/reuse-tool
rev: v4.0.3
rev: v5.0.2
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 21 (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

View File

@@ -51,9 +51,8 @@ dependencies {
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
// Forge Gradle
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion"))
runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion"))
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion")
runtimeOnly("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion")
// Fabric Loom
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$cctVersion")
@@ -61,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!

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/computercraft/turtle_upgrade/**",
"projects/common/src/testMod/resources/data/cctest/structures/**",
"projects/**/src/generated/**",
"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

@@ -24,21 +24,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) }
@@ -118,7 +116,7 @@ idea.project.settings.compiler.javac {
}
versionCatalogUpdate {
sortByKey.set(false)
sortByKey = false
pin { versions.addAll("fastutil", "guava", "netty", "slf4j") }
keep { keepUnusedLibraries.set(true) }
keep { keepUnusedLibraries = 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")
}
}
@@ -50,10 +42,9 @@ dependencies {
implementation(libs.spotless)
implementation(libs.fabric.loom)
implementation(libs.forgeGradle)
implementation(libs.ideaExt)
implementation(libs.librarian)
implementation(libs.minotaur)
implementation(libs.modDevGradle)
implementation(libs.vanillaExtract)
}
@@ -77,7 +68,7 @@ gradlePlugin {
}
versionCatalogUpdate {
sortByKey.set(false)
keep { keepUnusedLibraries.set(true) }
catalogFile.set(file("../gradle/libs.versions.toml"))
sortByKey = false
keep { keepUnusedLibraries = true }
catalogFile = file("../gradle/libs.versions.toml")
}

View File

@@ -4,10 +4,7 @@
/** Default configuration for Fabric projects. */
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations
import cc.tweaked.gradle.*
plugins {
`java-library`
@@ -30,7 +27,7 @@ repositories {
loom {
splitEnvironmentSourceSets()
splitModDependencies.set(true)
splitModDependencies = true
}
MinecraftConfigurations.setup(project)
@@ -67,3 +64,9 @@ dependencies {
tasks.ideaSyncTask {
doLast { IdeaRunConfigurations(project).patch() }
}
tasks.named("checkDependencyConsistency", DependencyCheck::class.java) {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
// Minecraft depends on asm, but Fabric forces it to a more recent version
override(libs.findLibrary("asm").get(), "9.8")
}

View File

@@ -6,25 +6,25 @@
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")
version = libs.findVersion("neoForge").get().toString()
accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
parchment {
minecraftVersion = libs.findVersion("parchmentMc").get().toString()
mappingsVersion = libs.findVersion("parchment").get().toString()
}
}
MinecraftConfigurations.setup(project)
@@ -32,13 +32,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

@@ -29,7 +29,7 @@ base.archivesName.convention("cc-tweaked-$mcVersion-${project.name}")
java {
toolchain {
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
languageVersion = CCTweakedPlugin.JAVA_VERSION
}
withSourcesJar()
@@ -44,18 +44,11 @@ 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("net.commoble.morered")
includeGroup("dev.architectury")
includeGroup("dev.emi")
includeGroup("maven.modrinth")
@@ -64,7 +57,6 @@ repositories {
includeGroup("mezz.jei")
includeGroup("org.teavm")
includeModule("com.terraformersmc", "modmenu")
includeModule("me.lucko", "fabric-permissions-api")
}
}
}
@@ -86,19 +78,28 @@ 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"))
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.
check("NullAway", CheckSeverity.ERROR)
option(
@@ -121,7 +122,6 @@ tasks.compileTestJava {
}
}
tasks.withType(JavaCompile::class.java).configureEach {
options.encoding = "UTF-8"
}
@@ -155,7 +155,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/21/docs/api/")
}
}
@@ -169,8 +169,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 +194,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 +219,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

@@ -24,16 +24,16 @@ val modVersion: String by extra
val mcVersion: String by extra
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,34 @@
//
// 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.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.

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,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,18 +223,23 @@ abstract class CCTweakedExtension(
).resolve().single()
}
/**
* Exclude a dependency from being published in Maven.
*/
fun exclude(dep: Dependency) {
excludedDeps.add(dep)
}
private fun <T> 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 {
@@ -272,20 +248,6 @@ abstract class CCTweakedExtension(
"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

@@ -42,6 +42,6 @@ class CCTweakedPlugin : Plugin<Project> {
}
companion object {
val JAVA_VERSION = JavaLanguageVersion.of(17)
val JAVA_VERSION = JavaLanguageVersion.of(21)
}
}

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,19 @@ abstract class DependencyCheck : DefaultTask() {
overrides.putAll(project.provider { mutableMapOf(module.get().module.toString() to 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].
*/

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

@@ -25,7 +25,6 @@ import javax.xml.xpath.XPathFactory
* 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
@@ -35,22 +34,6 @@ internal class IdeaRunConfigurations(project: Project) {
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")
@@ -58,10 +41,9 @@ internal class IdeaRunConfigurations(project: Project) {
Files.list(runConfigDir.toPath()).use {
for (configuration in it) {
val filename = configuration.fileName.toString();
val filename = configuration.fileName.toString()
when {
filename.endsWith("_fabric.xml") -> patchFabric(configuration)
filename.startsWith("forge_") && filename.endsWith(".xml") -> patchForge(configuration)
else -> {}
}
}
@@ -72,65 +54,6 @@ internal class IdeaRunConfigurations(project: Project) {
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) {
@@ -159,16 +82,5 @@ internal class IdeaRunConfigurations(project: Project) {
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

@@ -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,13 +36,7 @@ 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 {
@@ -85,6 +78,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 +99,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 +130,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

@@ -12,6 +12,7 @@ files:
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,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.114.2
isUnstable=true
modVersion=1.116.1
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.20.1
mcVersion=1.21.7

View File

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

View File

@@ -6,75 +6,76 @@
# 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.128.0+1.21.7"
fabric-loader = "0.16.14"
neoForge = "21.7.19-beta"
neoMergeTool = "2.0.0"
mixin = "0.8.5"
parchment = "2023.08.20"
parchmentMc = "1.20.1"
yarn = "1.20.1+build.10"
parchment = "2025.06.29"
parchmentMc = "1.21.6"
yarn = "1.21.7+build.1"
# 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.15"
guava = "33.3.1-jre"
netty = "4.1.118.Final"
slf4j = "2.0.16"
# Core dependencies (independent of Minecraft)
asm = "9.6"
asm = "9.7.1"
autoService = "1.1.1"
checkerFramework = "3.42.0"
cobalt = { strictly = "0.9.5" }
cobalt = { strictly = "0.9.6" }
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2"
jspecify = "1.0.0"
jzlib = "1.1.3"
kotlin = "1.9.21"
kotlin-coroutines = "1.7.3"
nightConfig = "3.6.7"
kotlin = "2.1.10"
kotlin-coroutines = "1.10.1"
nightConfig = "3.8.1"
# 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"
emi = "1.1.7+1.21"
fabricPermissions = "0.3.3"
iris-fabric = "1.9.1+1.21.7-fabric"
iris-forge = "1.9.1+1.21.7-neoforge"
jei = "23.1.0.4"
modmenu = "13.0.2"
moreRed = "6.0.0.3"
rei = "18.0.800"
sodium-fabric = "mc1.21.6-0.6.13-fabric"
sodium-forge = "mc1.21.6-0.6.13-neoforge"
mixinExtra = "0.3.5"
create-forge = "6.0.0-6"
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
# Testing
hamcrest = "2.2"
jqwik = "1.8.2"
junit = "5.10.1"
junit = "5.11.4"
junitPlatform = "1.11.4"
jmh = "1.37"
# Build tools
cctJavadoc = "1.8.3"
checkstyle = "10.14.1"
errorProne-core = "2.27.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.7.1"
forgeGradle = "6.0.21"
cctJavadoc = "1.8.5"
checkstyle = "10.23.1"
errorProne-core = "2.38.0"
errorProne-plugin = "4.1.0"
fabric-loom = "1.10.4"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-74-gf1551d5"
librarian = "1.+"
illuaminate = "0.1.0-83-g1131f68"
lwjgl = "3.3.3"
minotaur = "2.+"
nullAway = "0.10.25"
minotaur = "2.8.7"
modDevGradle = "2.0.99"
nullAway = "0.12.7"
shadow = "8.3.1"
spotless = "6.23.3"
spotless = "7.0.2"
taskTree = "2.1.1"
teavm = "0.11.0-SQUID.1"
vanillaExtract = "0.1.3"
teavm = "0.13.0-SQUID.1"
vanillaExtract = "0.2.1"
versionCatalogUpdate = "0.8.1"
[libraries]
@@ -86,10 +87,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" }
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
jzlib = { module = "com.jcraft:jzlib", version.ref = "jzlib" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
@@ -104,25 +105,26 @@ 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" }
create-forge = { module = "com.simibubi.create:create-1.21.1", version.ref = "create-forge" }
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
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-1.21.7-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.21.7-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.21.7-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" }
moreRed = { module = "net.commoble.morered:morered-1.21.1", version.ref = "moreRed" }
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" }
@@ -131,6 +133,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" }
@@ -150,12 +153,11 @@ errorProne-core = { module = "com.google.errorprone:error_prone_core", version.r
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
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" }
@@ -170,29 +172,27 @@ vanillaExtract = { module = "cc.tweaked.vanilla-extract:plugin", version.ref = "
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-common = ["iris-forge", "jei-api", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["moreRed", "iris-forge", "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-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"]

View File

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

3
gradlew vendored
View File

@@ -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

960
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,11 +12,11 @@
"tslib": "^2.0.3"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.1",
"@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": "^22.0.0",
"@types/node": "^24.0.0",
"lightningcss": "^1.22.0",
"preact-render-to-string": "^6.2.1",
"rehype": "^13.0.0",

View File

@@ -18,13 +18,28 @@ 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 Minecraft $mcVersion"
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",
@@ -47,8 +62,26 @@ tasks.javadoc {
<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,166 @@
// 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 dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.item.BlockModelWrapper;
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.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BlockModelRotation;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ResolvedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ARGB;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import org.joml.Vector3f;
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 BlockModelWrapper}, 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 TextureAtlasSprite particleIcon;
private final RenderType renderType;
private final Supplier<Vector3f[]> 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#setParticleIcon(TextureAtlasSprite)}.
* @param renderType The render type for this model.
*/
public StandaloneModel(List<BakedQuad> quads, boolean usesBlockLight, TextureAtlasSprite particleIcon, RenderType renderType) {
this.quads = quads;
this.useBlockLight = usesBlockLight;
this.particleIcon = particleIcon;
this.renderType = renderType;
this.extents = Suppliers.memoize(() -> BlockModelWrapper.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(ResourceLocation 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();
return new StandaloneModel(
model.bakeTopGeometry(slots, baker, BlockModelRotation.X0_Y0).getAll(),
model.getTopGuiLight().lightLikeBlock(),
model.resolveParticleSprite(slots, baker),
Sheets.translucentItemSheet()
);
}
/**
* 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, LivingEntity, int)
*/
public void setupItemLayer(ItemStackRenderState.LayerRenderState layer) {
layer.setExtents(extents);
layer.setRenderType(renderType);
layer.setUsesBlockLight(useBlockLight);
layer.setParticleIcon(particleIcon);
layer.prepareQuadList().addAll(quads);
}
/**
* Render the model directly.
*
* @param transform The current pose stack transformations.
* @param buffers The buffer source to use for rendering.
* @param light The current light texture coordinate.
* @param overlay The current overlay texture coordinate.
*/
public void render(PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
render(transform, buffers, light, overlay, null);
}
/**
* Render the model directly.
*
* @param transform The current pose stack transformations.
* @param buffers The buffer source to use for rendering.
* @param light The current light texture coordinate.
* @param overlay The current overlay texture coordinate.
* @param tints The tints for this model.
*/
public void render(PoseStack transform, MultiBufferSource buffers, int light, int overlay, int @Nullable [] tints) {
var pose = transform.last();
var buffer = buffers.getBuffer(renderType);
for (var quad : quads) {
float r, g, b, a;
var idx = quad.tintIndex();
if (tints != null && idx >= 0 && idx < tints.length) {
var tint = tints[idx];
r = ARGB.red(tint) / 255.0f;
g = ARGB.green(tint) / 255.0f;
b = ARGB.blue(tint) / 255.0f;
a = ARGB.alpha(tint) / 255.0f;
} else {
r = g = b = a = 1.0f;
}
buffer.putBulkData(pose, quad, r, g, b, a, light, overlay);
}
}
}

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,95 @@
// 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.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.StandaloneModel;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.model.ItemTransform;
import net.minecraft.client.renderer.item.BlockModelWrapper;
import net.minecraft.client.renderer.item.ItemModelResolver;
import net.minecraft.client.renderer.item.ItemStackRenderState;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.resources.ResourceLocation;
/**
* A {@link TurtleUpgradeModel} that renders a basic model.
* <p>
* This is the {@link TurtleUpgradeModel} equivalent of {@link BlockModelWrapper}.
*/
public final class BasicUpgradeModel implements TurtleUpgradeModel {
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "sided");
public static final MapCodec<? extends TurtleUpgradeModel.Unbaked> CODEC = RecordCodecBuilder.<Unbaked>mapCodec(instance -> instance.group(
ResourceLocation.CODEC.fieldOf("left").forGetter(Unbaked::left),
ResourceLocation.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(ResourceLocation left, ResourceLocation 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.setTransform(transform);
getModel(side).setupItemLayer(layer);
}
@Override
public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
getModel(side).render(transform, buffers, light, overlay);
}
private record Unbaked(ResourceLocation left, ResourceLocation 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,143 @@
// 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.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.model.ItemTransform;
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.core.component.DataComponentPatch;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.jspecify.annotations.Nullable;
import java.util.Set;
/**
* A sic {@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 ResourceLocation ID = ResourceLocation.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(), ItemDisplayContext.NONE, null, null, seed);
if (!childState.isEmpty()) {
renderer.appendModelIdentityElement(childState.getModelIdentity());
renderer.appendModelIdentityElement(transform);
var layer = renderer.newLayer();
layer.setTransform(transform);
layer.setupSpecialModel(getRenderer(side), childState);
}
}
@Override
public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
transform.mulPose(getRenderer(side).transform().getMatrix());
transform.mulPose(Axis.YP.rotation(Mth.PI));
Minecraft.getInstance().getItemRenderer().renderStatic(
upgrade.getUpgradeItem(), ItemDisplayContext.FIXED, light, overlay, transform, buffers, turtle.getLevel(), 0
);
}
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 render(
@Nullable ItemStackRenderState state, ItemDisplayContext itemDisplayContext, PoseStack poseStack,
MultiBufferSource multiBufferSource, int overlay, int light, boolean bl
) {
if (state == null) return;
poseStack.pushPose();
poseStack.mulPose(transform.getMatrix());
state.render(poseStack, multiBufferSource, overlay, light);
poseStack.popPose();
}
@Override
public void getExtents(Set<Vector3f> 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.ResourceLocation;
/**
* 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(ResourceLocation 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,209 @@
// 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.blaze3d.vertex.PoseStack;
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.ITurtleAccess;
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.Util;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.model.ItemTransform;
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.MissingBlockModel;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.resources.ResourceLocation;
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 ResourceLocation ID = ResourceLocation.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);
}
@Override
public void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay) {
getModel(upgrade).renderForLevel(upgrade, side, turtle, transform, buffers, light, overlay);
}
private record Unbaked<T>(
Cases<T> cases,
Optional<TurtleUpgradeModel.Unbaked> fallback
) implements TurtleUpgradeModel.Unbaked {
private static final TurtleUpgradeModel.Unbaked MISSING = BasicUpgradeModel.unbaked(MissingBlockModel.LOCATION, MissingBlockModel.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,110 @@
// 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.serialization.Codec;
import com.mojang.serialization.MapCodec;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.ITurtleAccess;
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.MultiBufferSource;
import net.minecraft.client.renderer.block.model.ItemTransform;
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.world.entity.LivingEntity;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
/**
* 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, LivingEntity, int)
*/
void renderForItem(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ItemStackRenderState renderer, ItemModelResolver resolver, ItemTransform transform, int seed);
/**
* Render this upgrade to a {@link MultiBufferSource}. This is used for rendering the block-entity form of the
* upgrade.
*
* @param upgrade The upgrade being rendered.
* @param side Which side of the turtle (left or right) the upgrade is equipped on.
* @param turtle Access to the turtle that the upgrade resides on.
* @param transform The current pose stack.
* @param buffers The buffers to render to.
* @param light The lightmap coordinate.
* @param overlay The overlay coordinate.
*/
void renderForLevel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side, ITurtleAccess turtle, PoseStack transform, MultiBufferSource buffers, int light, int overlay);
/**
* 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)
*/
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,7 @@ import net.minecraft.core.Direction;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* The static entry point to the ComputerCraft API.
@@ -143,16 +140,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 +158,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 class="language language-java">{@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

@@ -6,10 +6,12 @@ package dan200.computercraft.api;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.ItemTags;
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 +28,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 +51,16 @@ public class ComputerCraftTags {
*/
public static final TagKey<Item> TURTLE_CAN_PLACE = make("turtle_can_place");
/**
* Items which can be dyed.
* <p>
* This is similar to {@link ItemTags#DYEABLE}, but allows cleaning the item with a sponge, rather than in a
* cauldron.
*/
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, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
}
}
@@ -75,13 +99,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, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
}
}
}

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

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

View File

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

View File

@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.detail;
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 component The component to provide details for.
*/
public abstract void provideComponentDetails(Map<? super String, Object> data, T component);
@Override
public final void provideDetails(Map<? super String, Object> data, DataComponentHolder holder) {
var value = holder.get(component);
if (value == null) return;
if (namespace == null) {
provideComponentDetails(data, value);
} else {
Map<? super String, Object> child = new HashMap<>();
provideComponentDetails(child, value);
data.put(namespace, child);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(registries, 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

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

View File

@@ -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,8 +4,7 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
@@ -15,27 +14,20 @@ 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 Component adjective;
private final ItemStack stack;
protected AbstractPocketUpgrade(ResourceLocation id, String adjective, ItemStack stack) {
this.id = id;
protected AbstractPocketUpgrade(Component adjective, ItemStack stack) {
this.adjective = adjective;
this.stack = stack;
}
protected AbstractPocketUpgrade(ResourceLocation id, ItemStack stack) {
this(id, UpgradeBase.getDefaultAdjective(id), stack);
protected AbstractPocketUpgrade(String adjective, ItemStack stack) {
this(Component.translatable(adjective), stack);
}
@Override
public final ResourceLocation getUpgradeID() {
return id;
}
@Override
public final String getUnlocalisedAdjective() {
public final Component getAdjective() {
return adjective;
}

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.core.component.DataComponentPatch;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
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 #setUpgradeData(DataComponentPatch)
* @see UpgradeBase#getUpgradeItem(DataComponentPatch)
* @see UpgradeBase#getUpgradeData(ItemStack)
* @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.ResourceKey;
import net.minecraft.resources.ResourceLocation;
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(ResourceLocation.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);
}
}

View File

@@ -4,8 +4,7 @@
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
@@ -15,34 +14,27 @@ import net.minecraft.world.item.ItemStack;
* One does not have to use this, but it does provide a convenient template.
*/
public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade {
private final ResourceLocation id;
private final TurtleUpgradeType type;
private final String adjective;
private final Component adjective;
private final ItemStack stack;
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, String adjective, ItemStack stack) {
this.id = id;
protected AbstractTurtleUpgrade(TurtleUpgradeType type, Component adjective, ItemStack stack) {
this.type = type;
this.adjective = adjective;
this.stack = stack;
}
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, ItemStack stack) {
this(id, type, UpgradeBase.getDefaultAdjective(id), stack);
protected AbstractTurtleUpgrade(TurtleUpgradeType type, String adjective, ItemStack stack) {
this(type, 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 TurtleUpgradeType getType() {
public final TurtleUpgradeType getUpgradeType() {
return type;
}

View File

@@ -12,13 +12,12 @@ import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* The interface passed to turtle by turtles, providing methods that they can call.
@@ -229,37 +228,22 @@ public interface ITurtleAccess {
* @param side The side to get the upgrade from.
* @return The upgrade on the specified side of the turtle, if there is one.
* @see #getUpgradeWithData(TurtleSide)
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
* @see #setUpgrade(TurtleSide, UpgradeData)
*/
@Nullable
ITurtleUpgrade getUpgrade(TurtleSide side);
/**
* Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeNBTData(TurtleSide)
* Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeData(TurtleSide)
* update data}.
*
* @param side The side to get the upgrade from.
* @return The upgrade on the specified side of the turtle, along with its upgrade data, if there is one.
* @see #getUpgradeWithData(TurtleSide)
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
* @see #setUpgrade(TurtleSide, UpgradeData)
*/
default @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side) {
var upgrade = getUpgrade(side);
return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData(side));
}
/**
* Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
*
* @param side The side to set the upgrade on.
* @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgrade(TurtleSide)
* @deprecated Use {@link #setUpgradeWithData(TurtleSide, UpgradeData)}
*/
@Deprecated
default void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
setUpgradeWithData(side, upgrade == null ? null : UpgradeData.ofDefault(upgrade));
}
@Nullable
UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side);
/**
* Set the upgrade for a given side and its upgrade data.
@@ -268,7 +252,7 @@ public interface ITurtleAccess {
* @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgradeWithData(TurtleSide)
*/
void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
void setUpgrade(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
/**
* Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
@@ -282,23 +266,23 @@ public interface ITurtleAccess {
/**
* Get an upgrade-specific NBT compound, which can be used to store arbitrary data.
* <p>
* This will be persisted across turtle restarts and chunk loads, as well as being synced to the client. You must
* call {@link #updateUpgradeNBTData(TurtleSide)} after modifying it.
* This will be persisted across turtle restarts and chunk loads, as well as being synced to the client. You can
* call {@link #setUpgrade(TurtleSide, UpgradeData)} to modify it.
*
* @param side The side to get the upgrade data for.
* @return The upgrade-specific data.
* @see #updateUpgradeNBTData(TurtleSide)
* @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see #setUpgradeData(TurtleSide, DataComponentPatch)
* @see UpgradeBase#getUpgradeItem(DataComponentPatch)
* @see UpgradeBase#getUpgradeData(ItemStack)
*/
CompoundTag getUpgradeNBTData(TurtleSide side);
DataComponentPatch getUpgradeData(TurtleSide side);
/**
* Mark the upgrade-specific data as dirty on a specific side. This is required for the data to be synced to the
* client and persisted.
* Update the upgrade-specific data.
*
* @param side The side to mark dirty.
* @see #updateUpgradeNBTData(TurtleSide)
* @param side The side to set the upgrade data for.
* @param data The new upgrade data.
* @see #getUpgradeData(TurtleSide)
*/
void updateUpgradeNBTData(TurtleSide side);
void setUpgradeData(TurtleSide side, DataComponentPatch data);
}

View File

@@ -4,34 +4,126 @@
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeType;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistrySetBuilder.PatchedRegistries;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Items;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.function.Function;
/**
* The primary interface for defining an update for Turtles. A turtle update can either be a new tool, or a new
* peripheral.
* <p>
* Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding
* {@link TurtleUpgradeSerialiser} instance, which are then registered in a Forge registry.
* {@link 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 TurtleUpgradeSerialiser} for details on this process
* and where files should be located.
* the upgrade automatically registered.
*
* @see TurtleUpgradeSerialiser For how to register a turtle upgrade.
* <h2>Example</h2>
* <h3>Registering the upgrade type</h3>
* First, let's create a new class that implements {@link ITurtleUpgrade}. It is recommended to subclass
* {@link AbstractTurtleUpgrade}, as that provides a default implementation of most methods.
*
* {@snippet class=com.example.examplemod.ExampleTurtleUpgrade region=body}
*
* Now we must construct a new upgrade type. In most cases, you can use one of the helper methods (e.g.
* {@link UpgradeType#simpleWithCustomItem(Function)}), rather than defining your own implementation.
*
* {@snippet class=com.example.examplemod.ExampleMod region=turtle_upgrades}
*
* We now must register this upgrade type. This is done the same way as you'd register blocks, items, or other
* Minecraft objects. The approach to do this will depend on mod-loader.
*
* <h4>Fabric</h4>
* {@snippet class=com.example.examplemod.FabricExampleMod region=turtle_upgrades}
*
* <h4>Forge</h4>
* {@snippet class=com.example.examplemod.ForgeExampleMod region=turtle_upgrades}
*
* <h3 id="datagen">Registering the upgrade itself</h3>
* Upgrades themselves are loaded from datapacks when a level is loaded. In order to register our new upgrade, we must
* create a new JSON file at {@code data/<my_mod>/computercraft/turtle_upgrade/<my_upgrade_id>.json}.
*
* {@snippet file=data/examplemod/computercraft/turtle_upgrade/example_turtle_upgrade.json}
*
* The {@code "type"} field points to the ID of the upgrade type we've just registered, while the other fields are read
* by the type itself. As our upgrade was defined with {@link UpgradeType#simpleWithCustomItem(Function)}, the
* {@code "item"} field will construct our upgrade with {@link Items#COMPASS}.
* <p>
* Similarly, {@linkplain dan200.computercraft.api.client.turtle.TurtleUpgradeModel the upgrade's model} is loaded from
* a resource pack, and so we must also create a new JSON file at
* {@code assets/<my_mod>/computercraft/turtle_upgrade/<my_upgrade_id>.json}.
*
* {@snippet file=assets/examplemod/computercraft/turtle_upgrade/example_turtle_upgrade.json}
*
* Rather than manually creating these file, it is recommended to use data-generators to generate this file. First, we
* register our new upgrades into a {@linkplain PatchedRegistries patched registry}. Models must similarly be
* registered.
*
* {@snippet class=com.example.examplemod.data.TurtleUpgradeProvider region=body}
*
* Next, we must write these upgrades to disk. Vanilla does not have complete support for this yet, so this must be done
* with mod-loader-specific APIs.
*
* <h4>Fabric</h4>
* {@snippet class=com.example.examplemod.FabricExampleModDataGenerator region=turtle_upgrades}
*
* <h4>Forge</h4>
* {@snippet class=com.example.examplemod.ForgeExampleModDataGenerator region=turtle_upgrades}
*/
public interface ITurtleUpgrade extends UpgradeBase {
/**
* The registry in which turtle upgrades are stored.
*/
ResourceKey<Registry<ITurtleUpgrade>> REGISTRY = ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_upgrade"));
/**
* Create a {@link ResourceKey} for a turtle upgrade given a {@link ResourceLocation}.
* <p>
* This should only be called from within data generation code. Do not hard code references to your upgrades!
*
* @param id The id of the turtle upgrade.
* @return The upgrade registry key.
*/
static ResourceKey<ITurtleUpgrade> createKey(ResourceLocation id) {
return ResourceKey.create(REGISTRY, id);
}
/**
* The registry key for turtle upgrade types.
*
* @return The registry key.
*/
static ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> typeRegistry() {
return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
}
/**
* Get the type of this upgrade.
*
* @return The type of this upgrade.
*/
@Override
UpgradeType<? extends ITurtleUpgrade> getType();
/**
* Return whether this turtle adds a tool or a peripheral to the turtle.
*
* @return The type of upgrade this is.
* @see TurtleUpgradeType for the differences between them.
*/
TurtleUpgradeType getType();
TurtleUpgradeType getUpgradeType();
/**
* Will only be called for peripheral upgrades. Creates a peripheral for a turtle being placed using this upgrade.
@@ -90,7 +182,7 @@ public interface ITurtleUpgrade extends UpgradeBase {
* @param upgradeData Data that currently stored for this upgrade
* @return Filtered version of this data.
*/
default CompoundTag getPersistedData(CompoundTag upgradeData) {
default DataComponentPatch getPersistedData(DataComponentPatch upgradeData) {
return upgradeData;
}
}

View File

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

View File

@@ -4,17 +4,33 @@
package dan200.computercraft.api.turtle;
import com.mojang.serialization.Codec;
import net.minecraft.util.StringRepresentable;
/**
* An enum representing the two sides of the turtle that a turtle upgrade might reside.
*/
public enum TurtleSide {
public enum TurtleSide implements StringRepresentable {
/**
* The turtle's left side (where the pickaxe usually is on a Wireless Mining Turtle).
*/
LEFT,
LEFT("left"),
/**
* The turtle's right side (where the modem usually is on a Wireless Mining Turtle).
*/
RIGHT,
RIGHT("right");
public static final Codec<TurtleSide> CODEC = StringRepresentable.fromEnum(TurtleSide::values);
private final String name;
TurtleSide(String name) {
this.name = name;
}
@Override
public String getSerializedName() {
return name;
}
}

View File

@@ -0,0 +1,149 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.upgrades.TurtleToolSpec;
import net.minecraft.core.component.DataComponents;
import net.minecraft.data.worldgen.BootstrapContext;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import org.jspecify.annotations.Nullable;
import java.util.Optional;
/**
* A builder for custom turtle tool upgrades.
* <p>
* This can be used from your <a href="./ITurtleUpgrade.html#datagen">data generator</a> code in order to
* register turtle tools for your mod's tools.
*
* <h2>Example</h2>
* {@snippet class=com.example.examplemod.data.TurtleToolProvider region=body}
*/
public final class TurtleToolBuilder {
private final ResourceKey<ITurtleUpgrade> id;
private final Item item;
private Component adjective;
private float damageMultiplier = TurtleToolSpec.DEFAULT_DAMAGE_MULTIPLIER;
private @Nullable TagKey<Block> breakable;
private boolean allowEnchantments = false;
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
private TurtleToolBuilder(ResourceKey<ITurtleUpgrade> id, Item item) {
this.id = id;
adjective = Component.translatable(UpgradeBase.getDefaultAdjective(id.location()));
this.item = item;
}
public static TurtleToolBuilder tool(ResourceLocation id, Item item) {
return new TurtleToolBuilder(ITurtleUpgrade.createKey(id), item);
}
public static TurtleToolBuilder tool(ResourceKey<ITurtleUpgrade> id, Item item) {
return new TurtleToolBuilder(id, item);
}
/**
* Get the id for this turtle tool.
*
* @return The upgrade id.
*/
public ResourceKey<ITurtleUpgrade> id() {
return id;
}
/**
* Specify a custom adjective for this tool. By default this takes its adjective from the upgrade id.
*
* @param adjective The new adjective to use.
* @return The tool builder, for further use.
*/
public TurtleToolBuilder adjective(Component adjective) {
this.adjective = adjective;
return this;
}
/**
* The amount of damage a swing of this tool will do. This is multiplied by {@link Attributes#ATTACK_DAMAGE} to
* get the final damage.
*
* @param damageMultiplier The damage multiplier.
* @return The tool builder, for further use.
*/
public TurtleToolBuilder damageMultiplier(float damageMultiplier) {
this.damageMultiplier = damageMultiplier;
return this;
}
/**
* Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
* {@linkplain DataComponents#ATTRIBUTE_MODIFIERS custom attribute modifiers}.
*
* @return The tool builder, for further use.
*/
public TurtleToolBuilder allowEnchantments() {
allowEnchantments = true;
return this;
}
/**
* Set when the tool will consume durability.
*
* @param durability The durability predicate.
* @return The tool builder, for further use.
*/
public TurtleToolBuilder consumeDurability(TurtleToolDurability durability) {
consumeDurability = durability;
return this;
}
/**
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
* be broken.
*
* @param breakable The tag containing all blocks breakable by this item.
* @return The tool builder, for further use.
* @see ComputerCraftTags.Blocks
*/
public TurtleToolBuilder breakable(TagKey<Block> breakable) {
this.breakable = breakable;
return this;
}
/**
* Build the turtle tool upgrade.
*
* @return The constructed upgrade.
*/
public ITurtleUpgrade build() {
return ComputerCraftAPIService.get().createTurtleTool(new TurtleToolSpec(
adjective,
item,
damageMultiplier,
allowEnchantments,
consumeDurability,
Optional.ofNullable(breakable)
));
}
/**
* Build this upgrade and register it for datagen.
*
* @param upgrades The registry this upgrade should be added to.
*/
public void register(BootstrapContext<ITurtleUpgrade> upgrades) {
upgrades.register(id(), build());
}
}

View File

@@ -4,14 +4,14 @@
package dan200.computercraft.api.turtle;
import net.minecraft.core.component.DataComponents;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack;
/**
* Indicates if an equipped turtle item will consume durability.
*
* @see TurtleUpgradeDataProvider.ToolBuilder#consumeDurability(TurtleToolDurability)
* @see TurtleToolBuilder#consumeDurability(TurtleToolDurability)
*/
public enum TurtleToolDurability implements StringRepresentable {
/**
@@ -21,7 +21,7 @@ public enum TurtleToolDurability implements StringRepresentable {
/**
* The equipped tool consumes durability if it is {@linkplain ItemStack#isEnchanted() enchanted} or has
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
* {@linkplain DataComponents#ATTRIBUTE_MODIFIERS custom attribute modifiers}.
*/
WHEN_ENCHANTED("when_enchanted"),

View File

@@ -1,168 +0,0 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import javax.annotation.Nullable;
import java.util.function.Consumer;
/**
* A data provider to generate turtle 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 TurtleUpgradeSerialiser
*/
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {
private static final ResourceLocation TOOL_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "tool");
public TurtleUpgradeDataProvider(PackOutput output) {
super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.registryId());
}
/**
* Create a new turtle tool upgrade, such as a pickaxe or shovel.
*
* @param id The ID of this tool.
* @param item The item used for tool actions. Note, this doesn't inherit all properties of the tool, you may need
* to specify {@link ToolBuilder#damageMultiplier(float)} and {@link ToolBuilder#breakable(TagKey)}.
* @return A tool builder,
*/
public final ToolBuilder tool(ResourceLocation id, Item item) {
return new ToolBuilder(id, existingSerialiser(TOOL_ID), item);
}
/**
* A builder for custom turtle tool upgrades.
*
* @see #tool(ResourceLocation, Item)
*/
public static class ToolBuilder {
private final ResourceLocation id;
private final TurtleUpgradeSerialiser<?> serialiser;
private final Item toolItem;
private @Nullable String adjective;
private @Nullable Item craftingItem;
private @Nullable Float damageMultiplier = null;
private @Nullable TagKey<Block> breakable;
private boolean allowEnchantments = false;
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
this.id = id;
this.serialiser = serialiser;
this.toolItem = toolItem;
craftingItem = null;
}
/**
* Specify a custom adjective for this tool. By default this takes its adjective from the tool item.
*
* @param adjective The new adjective to use.
* @return The tool builder, for further use.
*/
public ToolBuilder adjective(String adjective) {
this.adjective = adjective;
return this;
}
/**
* Specify a custom item which is used to craft this upgrade. By default this is the same as the provided tool
* item, but you may wish to override it.
*
* @param craftingItem The item used to craft this upgrade.
* @return The tool builder, for further use.
*/
public ToolBuilder craftingItem(Item craftingItem) {
this.craftingItem = craftingItem;
return this;
}
/**
* The amount of damage a swing of this tool will do. This is multiplied by {@link Attributes#ATTACK_DAMAGE} to
* get the final damage.
*
* @param damageMultiplier The damage multiplier.
* @return The tool builder, for further use.
*/
public ToolBuilder damageMultiplier(float damageMultiplier) {
this.damageMultiplier = damageMultiplier;
return this;
}
/**
* Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
*
* @return The tool builder, for further use.
*/
public ToolBuilder allowEnchantments() {
allowEnchantments = true;
return this;
}
/**
* Set when the tool will consume durability.
*
* @param durability The durability predicate.
* @return The tool builder, for further use.
*/
public ToolBuilder consumeDurability(TurtleToolDurability durability) {
consumeDurability = durability;
return this;
}
/**
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
* be broken.
*
* @param breakable The tag containing all blocks breakable by this item.
* @return The tool builder, for further use.
* @see ComputerCraftTags.Blocks
*/
public ToolBuilder breakable(TagKey<Block> breakable) {
this.breakable = breakable;
return this;
}
/**
* Register this as an upgrade.
*
* @param add The callback given to {@link #addUpgrades(Consumer)}.
*/
public void add(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add) {
add.accept(new Upgrade<>(id, serialiser, s -> {
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, toolItem).toString());
if (adjective != null) s.addProperty("adjective", adjective);
if (craftingItem != null) {
s.addProperty("craftItem", PlatformHelper.get().getRegistryKey(Registries.ITEM, craftingItem).toString());
}
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
if (breakable != null) s.addProperty("breakable", breakable.location().toString());
if (allowEnchantments) s.addProperty("allowEnchantments", true);
if (consumeDurability != TurtleToolDurability.NEVER) {
s.addProperty("consumeDurability", consumeDurability.getSerializedName());
}
}));
}
}
}

View File

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

View File

@@ -9,37 +9,34 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import java.util.Objects;
/**
* Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}.
*/
public interface UpgradeBase {
/**
* Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem"
* or "my_mod:my_upgrade".
* <p>
* You should use a unique resource domain to ensure this upgrade is uniquely identified.
* The upgrade will fail registration if an already used ID is specified.
* Get the type of this upgrade.
*
* @return The unique ID for this upgrade.
* @return The type of this upgrade.
*/
ResourceLocation getUpgradeID();
UpgradeType<?> getType();
/**
* Return an unlocalised string to describe this type of computer in item names.
* A description of this upgrade for use in item names.
* <p>
* This should typically be a {@linkplain Component#translatable(String) translation key}, rather than a hard coded
* string.
* <p>
* Examples of built-in adjectives are "Wireless", "Mining" and "Crafty".
*
* @return The localisation key for this upgrade's adjective.
* @return The text component for this upgrade's adjective.
*/
String getUnlocalisedAdjective();
Component getAdjective();
/**
* Return an item stack representing the type of item that a computer must be crafted
@@ -57,8 +54,8 @@ public interface UpgradeBase {
/**
* Returns the item stack representing a currently equipped turtle upgrade.
* <p>
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} and
* {@link IPocketAccess#getUpgradeNBTData()}}, by default this data is discarded when an upgrade is unequipped,
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeData(TurtleSide)} and
* {@link IPocketAccess#getUpgradeData()}}, by default this data is discarded when an upgrade is unequipped,
* and the original item stack is returned.
* <p>
* By overriding this method, you can create a new {@link ItemStack} which contains enough data to
@@ -70,24 +67,24 @@ public interface UpgradeBase {
* @param upgradeData The current upgrade data. This should <strong>NOT</strong> be mutated.
* @return The item stack returned when unequipping.
*/
default ItemStack getUpgradeItem(CompoundTag upgradeData) {
default ItemStack getUpgradeItem(DataComponentPatch upgradeData) {
return getCraftingItem();
}
/**
* Extract upgrade data from an {@link ItemStack}.
* <p>
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
* {@link IPocketAccess#getUpgradeNBTData()}.
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeData(TurtleSide)} or
* {@link IPocketAccess#getUpgradeData()}.
* <p>
* This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
* This should be an inverse to {@link #getUpgradeItem(DataComponentPatch)}.
*
* @param stack The stack that was equipped by the turtle or pocket computer. This will have the same item as
* {@link #getCraftingItem()}.
* @return The upgrade data that should be set on the turtle or pocket computer.
*/
default CompoundTag getUpgradeData(ItemStack stack) {
return new CompoundTag();
default DataComponentPatch getUpgradeData(ItemStack stack) {
return DataComponentPatch.EMPTY;
}
/**
@@ -97,26 +94,15 @@ public interface UpgradeBase {
* the original stack. In order to prevent people losing items with enchantments (or
* repairing items with non-0 damage), we impose additional checks on the item.
* <p>
* The default check requires that any non-capability NBT is exactly the same as the
* crafting item, but this may be relaxed for your upgrade.
* <p>
* This is based on {@code net.minecraftforge.common.crafting.StrictNBTIngredient}'s check.
* The default check requires that any NBT is exactly the same as the crafting item,
* but this may be relaxed for your upgrade.
*
* @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
* {@link #getCraftingItem()}.
* @return If this stack may be used to equip this upgrade.
*/
default boolean isItemSuitable(ItemStack stack) {
var crafting = getCraftingItem();
// A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
// null one.
var shareTag = PlatformHelper.get().getShareTag(stack);
var craftingShareTag = PlatformHelper.get().getShareTag(crafting);
if (shareTag == craftingShareTag) return true;
if (shareTag == null) return Objects.requireNonNull(craftingShareTag).isEmpty();
if (craftingShareTag == null) return shareTag.isEmpty();
return shareTag.equals(craftingShareTag);
return ItemStack.isSameItemSameComponents(getCraftingItem(), stack);
}
/**
@@ -125,7 +111,7 @@ public interface UpgradeBase {
*
* @param id The upgrade ID.
* @return The generated adjective.
* @see #getUnlocalisedAdjective()
* @see #getAdjective()
*/
static String getDefaultAdjective(ResourceLocation id) {
return Util.makeDescriptionId("upgrade", id) + ".adjective";

View File

@@ -6,60 +6,62 @@ package dan200.computercraft.api.upgrades;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponentGetter;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.
* <p>
* <strong>IMPORTANT:</strong> The {@link #data()} in an upgrade data is often a reference to the original upgrade data.
* Be careful to take a {@linkplain #copy() defensive copy} if you plan to use the data in this upgrade.
*
* @param upgrade The current upgrade.
* @param data The upgrade's data.
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
* @param holder The current upgrade holder.
* @param data The upgrade's data.
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
*/
public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
public record UpgradeData<T extends UpgradeBase>(
Holder.Reference<T> holder, DataComponentPatch data
) implements DataComponentGetter {
/**
* A utility method to construct a new {@link UpgradeData} instance.
*
* @param upgrade An upgrade.
* @param data The upgrade's data.
* @param <T> The type of upgrade.
* @param holder An upgrade.
* @param data The upgrade's data.
* @param <T> The type of upgrade.
* @return The new {@link UpgradeData} instance.
*/
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
return new UpgradeData<>(upgrade, data);
public static <T extends UpgradeBase> UpgradeData<T> of(Holder.Reference<T> holder, DataComponentPatch data) {
return new UpgradeData<>(holder, data);
}
/**
* Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
*
* @param upgrade The upgrade instance.
* @param <T> The type of upgrade.
* @param holder The upgrade instance.
* @param <T> The type of upgrade.
* @return The default upgrade data.
*/
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(Holder.Reference<T> holder) {
var upgrade = holder.value();
return of(holder, upgrade.getUpgradeData(upgrade.getCraftingItem()));
}
public UpgradeData {
if (!holder.isBound()) throw new IllegalArgumentException("Holder is not bound");
}
/**
* Take a copy of a (possibly {@code null}) {@link UpgradeData} instance.
* Get the current upgrade.
*
* @param upgrade The copied upgrade data.
* @param <T> The type of upgrade.
* @return The newly created upgrade data.
* @return The current upgrade.
*/
@Contract("!null -> !null; null -> null")
public static <T extends UpgradeBase> @Nullable UpgradeData<T> copyOf(@Nullable UpgradeData<T> upgrade) {
return upgrade == null ? null : upgrade.copy();
public T upgrade() {
return holder().value();
}
/**
* Get the {@linkplain UpgradeBase#getUpgradeItem(CompoundTag) upgrade item} for this upgrade.
* Get the {@linkplain UpgradeBase#getUpgradeItem(DataComponentPatch) upgrade item} for this upgrade.
* <p>
* This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original
* {@linkplain UpgradeBase#getCraftingItem() upgrade stack}.
@@ -67,16 +69,19 @@ public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
* @return This upgrade's item.
*/
public ItemStack getUpgradeItem() {
return upgrade.getUpgradeItem(data).copy();
return upgrade().getUpgradeItem(data).copy();
}
/**
* Take a copy of this {@link UpgradeData}. This returns a new instance with the same upgrade and a fresh copy of
* the upgrade data.
* Get a component from the upgrade's {@link #data()} .
*
* @return A copy of the current instance.
* @param component The component get.
* @param <U> The type of the component's value.
* @return The component, or {@code null} if not present.
*/
public UpgradeData<T> copy() {
return new UpgradeData<>(upgrade(), data().copy());
@Override
public <U> @Nullable U get(DataComponentType<? extends U> component) {
var result = data().get(component);
return result == null ? null : result.orElse(null);
}
}

View File

@@ -1,179 +0,0 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.upgrades;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.PlatformHelper;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.Util;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* A data generator/provider for turtle and pocket computer upgrades. This should not be extended directly, instead see
* the other subclasses.
*
* @param <T> The base class of upgrades.
* @param <R> The upgrade serialiser to register for.
*/
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
private final PackOutput output;
private final String name;
private final String folder;
private final ResourceKey<Registry<R>> registry;
private @Nullable List<T> upgrades;
protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<R>> registry) {
this.output = output;
this.name = name;
this.folder = folder;
this.registry = registry;
}
/**
* Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
*
* @param id The ID of the upgrade to create.
* @param serialiser The simple serialiser.
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
*/
public final Upgrade<R> simple(ResourceLocation id, R serialiser) {
if (!(serialiser instanceof SimpleSerialiser)) {
throw new IllegalStateException(serialiser + " must be a simple() seriaiser.");
}
return new Upgrade<>(id, serialiser, s -> {
});
}
/**
* Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
*
* @param id The ID of the upgrade to create.
* @param serialiser The simple serialiser.
* @param item The crafting upgrade for this item.
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
*/
public final Upgrade<R> simpleWithCustomItem(ResourceLocation id, R serialiser, Item item) {
if (!(serialiser instanceof SerialiserWithCraftingItem)) {
throw new IllegalStateException(serialiser + " must be a simpleWithCustomItem() serialiser.");
}
return new Upgrade<>(id, serialiser, s ->
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, item).toString())
);
}
/**
* Add all turtle or pocket computer upgrades.
* <p>
* <strong>Example usage:</strong>
* <pre class="language language-java">{@code
* protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
* simple(new ResourceLocation("mymod", "speaker"), SPEAKER_SERIALISER.get()).add(addUpgrade);
* }
* }</pre>
*
* @param addUpgrade A callback used to register an upgrade.
*/
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
@Override
public CompletableFuture<?> run(CachedOutput cache) {
var base = output.getOutputFolder().resolve("data");
Set<ResourceLocation> seen = new HashSet<>();
List<T> upgrades = new ArrayList<>();
List<CompletableFuture<?>> futures = new ArrayList<>();
addUpgrades(upgrade -> {
if (!seen.add(upgrade.id())) throw new IllegalStateException("Duplicate upgrade " + upgrade.id());
var json = new JsonObject();
json.addProperty("type", PlatformHelper.get().getRegistryKey(registry, upgrade.serialiser()).toString());
upgrade.serialise().accept(json);
futures.add(DataProvider.saveStable(cache, json, base.resolve(upgrade.id().getNamespace() + "/" + folder + "/" + upgrade.id().getPath() + ".json")));
try {
var result = upgrade.serialiser().fromJson(upgrade.id(), json);
upgrades.add(result);
} catch (IllegalArgumentException | JsonParseException e) {
LOGGER.error("Failed to parse {} {}", name, upgrade.id(), e);
}
});
this.upgrades = Collections.unmodifiableList(upgrades);
return Util.sequenceFailFast(futures);
}
@Override
public final String getName() {
return name;
}
public final R existingSerialiser(ResourceLocation id) {
var result = PlatformHelper.get().getRegistryObject(registry, id);
if (result == null) throw new IllegalArgumentException("No such serialiser " + registry);
return result;
}
public List<T> getGeneratedUpgrades() {
if (upgrades == null) throw new IllegalStateException("Upgrades have not been generated yet");
return upgrades;
}
/**
* A constructed upgrade instance, produced {@link #addUpgrades(Consumer)}.
*
* @param id The ID for this upgrade.
* @param serialiser The serialiser which reads and writes this upgrade.
* @param serialise Augment the generated JSON with additional fields.
* @param <R> The type of upgrade serialiser.
*/
public record Upgrade<R extends UpgradeSerialiser<?>>(
ResourceLocation id, R serialiser, Consumer<JsonObject> serialise
) {
/**
* Convenience method for registering an upgrade.
*
* @param add The callback given to {@link #addUpgrades(Consumer)}
*/
public void add(Consumer<Upgrade<R>> add) {
add.accept(this);
}
/**
* Return a new {@link Upgrade} which requires the given mod to be present.
* <p>
* This uses mod-loader-specific hooks (Forge's crafting conditions and Fabric's resource conditions). If using
* this in a multi-loader setup, you must generate resources separately for the two loaders.
*
* @param modId The id of the mod.
* @return A new upgrade instance.
*/
public Upgrade<R> requireMod(String modId) {
return new Upgrade<>(id, serialiser, json -> {
PlatformHelper.get().addRequiredModCondition(json, modId);
serialise.accept(json);
});
}
}
}

View File

@@ -1,52 +0,0 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.upgrades;
import com.google.gson.JsonObject;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
/**
* Base interface for upgrade serialisers. This should generally not be implemented directly, instead implementing one
* of {@link TurtleUpgradeSerialiser} or {@link PocketUpgradeSerialiser}.
* <p>
* However, it may sometimes be useful to implement this if you have some shared logic between upgrade types.
*
* @param <T> The upgrade that this class can serialise and deserialise.
* @see TurtleUpgradeSerialiser
* @see PocketUpgradeSerialiser
*/
public interface UpgradeSerialiser<T extends UpgradeBase> {
/**
* Read this upgrade from a JSON file in a datapack.
*
* @param id The ID of this upgrade.
* @param object The JSON object to load this upgrade from.
* @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}.
* @see net.minecraft.util.GsonHelper For additional JSON helper methods.
*/
T fromJson(ResourceLocation id, JsonObject object);
/**
* Read this upgrade from a network packet, sent from the server.
*
* @param id The ID of this upgrade.
* @param buffer The buffer object to read this upgrade from.
* @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}.
*/
T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer);
/**
* Write this upgrade to a network packet, to be sent to the client.
*
* @param buffer The buffer object to write this upgrade to
* @param upgrade The upgrade to write.
*/
void toNetwork(FriendlyByteBuf buffer, T upgrade);
}

View File

@@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.upgrades;
import com.mojang.serialization.MapCodec;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.level.storage.loot.functions.LootItemFunction;
import java.util.function.Function;
/**
* The type of a {@linkplain ITurtleUpgrade turtle} or {@linkplain IPocketUpgrade pocket} upgrade.
* <p>
* Turtle and pocket computer upgrades are registered using Minecraft's dynamic registry system. As a result, they
* follow a similar design to other dynamic content, such as {@linkplain Recipe recipes} or {@link LootItemFunction
* loot functions}.
* <p>
* While the {@link ITurtleUpgrade}/{@link IPocketUpgrade} class should contain the core logic of the upgrade, they are
* not registered directly. Instead, each upgrade class has a corresponding {@link UpgradeType}, which is responsible
* for loading the upgrade from a datapack. The upgrade type should then be registered in its appropriate registry
* ({@link ITurtleUpgrade#typeRegistry()}, {@link IPocketUpgrade#typeRegistry()}).
* <p>
* In order to register the actual upgrade, a JSON file referencing your upgrade type should be added to a datapack. It
* is recommended to do this via the data generators.
*
* <h2 id="datagen">Data Generation</h2>
* As turtle and pocket upgrades are just loaded using vanilla's dynamic loaders, one may use the same data generation
* tools as you would for any other dynamic registry.
* <p>
* See <a href="../turtle/ITurtleUpgrade.html#datagen">the turtle upgrade docs</a> for a concrete example.
*
* @param <T> The upgrade subclass that this upgrade type represents.
* @see ITurtleUpgrade
* @see IPocketUpgrade
*/
public sealed interface UpgradeType<T extends UpgradeBase> permits UpgradeTypeImpl {
/**
* The codec to read and write this upgrade from a datapack.
*
* @return The codec for this upgrade.
*/
MapCodec<T> codec();
/**
* Create a new upgrade type.
*
* @param codec The codec
* @param <T> The type of the generated upgrade.
* @return The newly created upgrade type.
*/
static <T extends UpgradeBase> UpgradeType<T> create(MapCodec<T> codec) {
return new UpgradeTypeImpl<>(codec);
}
/**
* Create an upgrade type for an upgrade that takes no arguments.
* <p>
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(Function)} instead.
*
* @param instance Generate a new upgrade with a specific ID.
* @param <T> The type of the generated upgrade.
* @return A new upgrade type.
*/
static <T extends UpgradeBase> UpgradeType<T> simple(T instance) {
return create(MapCodec.unit(instance));
}
/**
* Create an upgrade type 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 A new upgrade type.
* @see #simple(UpgradeBase) For upgrades whose crafting stack should not vary.
*/
static <T extends UpgradeBase> UpgradeType<T> simpleWithCustomItem(Function<ItemStack, T> factory) {
return create(BuiltInRegistries.ITEM.byNameCodec()
.xmap(x -> factory.apply(new ItemStack(x)), x -> x.getCraftingItem().getItem())
.fieldOf("item"));
}
}

View File

@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.upgrades;
import com.mojang.serialization.MapCodec;
/**
* Simple implementation of {@link UpgradeType}.
*
* @param codec The codec to read/write upgrades with.
* @param <T> The upgrade subclass that this upgrade type represents.
*/
record UpgradeTypeImpl<T extends UpgradeBase>(MapCodec<T> codec) implements UpgradeType<T> {
}

View File

@@ -4,6 +4,7 @@
package dan200.computercraft.impl;
import com.mojang.serialization.Codec;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.api.detail.DetailRegistry;
@@ -11,14 +12,16 @@ import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.media.PrintoutContents;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeType;
import dan200.computercraft.impl.upgrades.TurtleToolSpec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
@@ -27,8 +30,7 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Backing interface for {@link ComputerCraftAPI}
@@ -57,8 +59,6 @@ public interface ComputerCraftAPIService {
int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side);
void registerMediaProvider(MediaProvider provider);
PacketNetwork getWirelessNetwork(MinecraftServer server);
void registerAPIFactory(ILuaAPIFactory factory);
@@ -67,14 +67,23 @@ public interface ComputerCraftAPIService {
void registerRefuelHandler(TurtleRefuelHandler handler);
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId();
ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId();
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId();
Codec<ITurtleUpgrade> turtleUpgradeCodec();
ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> pocketUpgradeRegistryId();
ITurtleUpgrade createTurtleTool(TurtleToolSpec spec);
Codec<IPocketUpgrade> pocketUpgradeCodec();
DetailRegistry<ItemStack> getItemStackDetailRegistry();
DetailRegistry<BlockReference> getBlockInWorldDetailRegistry();
@Nullable
PrintoutContents getPrintoutContents(ItemStack stack);
final class Instance {
static final @Nullable ComputerCraftAPIService INSTANCE;
static final @Nullable Throwable ERROR;

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