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

Compare commits

...

154 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

We now add a new class (ComputerEvents), which has a series of *static*
methods, that can queue an event on a ComputerEvents.Receiver object.
This is a bit of an odd indirection (why not just make them instance
methods on Receiver?!), but I don't really want those methods leaking
everywhere.
2025-01-18 19:26:10 +00:00
Jonathan Coates
6739c4c6c0 Wait for computers to run each tick in gametests 2025-01-17 18:43:19 +00:00
Jonathan Coates
d6749f8461 Set issue type in the templates
We already have the label, so it's not quite clear if it's worth it, but
let make our issue board look even more like a tube of smarties.
2025-01-17 17:25:39 +00:00
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
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
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
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
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
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
1963e0160f Bump CC:T to 1.114.2
Ahhhh, I hate graphics code.
2024-11-23 09:34:03 +00:00
Jonathan Coates
9a06904634 Only skip cursor if it exists
If the cursor is not visible then we'd end up blinking the last
character on the screen. And if the screen was empty we'd spew the logs
with GL errors.
2024-11-23 09:31:04 +00:00
Jonathan Coates
5e24ad17d7 Bump CC:T to 1.114.1 2024-11-23 09:14:53 +00:00
Jonathan Coates
8b1cb09ddf Update translations 2024-11-23 09:10:50 +00:00
Jonathan Coates
d1a6b043c2 Clean up monitor cursor rendering
- Use the correct index count for the cursor quad. Monitors are now
   rendered as quads, rather than triangles.
 - *Skip* rendering the cursor vertex, rather than additionally
   rendering it.

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

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

Closes #2014
2024-11-19 21:35:00 +00:00
Jonathan Coates
1d7d8006d4 Stop publishing to CurseForge
Been dragging my feet over this for a while now, but increasingly
uncomfortable with Overwolf. I'm not going to delete the project (or any
existing versions), just not publish any new versions there.
2024-11-16 15:19:00 +00:00
Jonathan Coates
63bdc2537c Fix monitor events using the wrong computer set
Fixes #2010
2024-11-15 18:43:28 +00:00
Jonathan Coates
f776b17150 Fix argument order of math.atan in changelog 2024-11-15 15:32:40 +00:00
Jonathan Coates
31da2555cb Bump CC:T to 1.114.0 2024-11-15 08:22:05 +00:00
Jonathan Coates
9b19a93ab9 Sync translations from Crowdin
Ended up writing my own janky script to do this, rather than use the GH
integration. I'm not proud of this, but it works.
2024-11-15 08:14:06 +00:00
Jonathan Coates
ad52117f0f Syntax highlight more docstrings
This is going to conflict horribly with MC 1.21 (where we started using
@snippet), but hopefully this still helps.
2024-11-13 11:00:46 +00:00
Jonathan Coates
bdffabc08e Clarify docs around registering peripherals 2024-11-13 10:19:10 +00:00
Jonathan Coates
87ce41f251 Update Cobalt to 0.9.5
- Fix several issues with large doubles
 - Fix metatable cache not being cleared
2024-11-12 21:11:22 +00:00
Jonathan Coates
e7c7919cad Add test for turtle crafting with remainders
See #2007. This isn't an issue on 1.20.1, so it doesn't fix the issue,
but good to have the test everywhere.
2024-11-12 09:17:03 +00:00
Jonathan Coates
4f66ac79d3 Add redstone relay block (#2002)
- Move redstone methods out of the IAPIEnvironment, and into a new
   RedstoneAccess. We similarly move the implementation from Environment
   into a new RedstoneState class.

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

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

 - Add the new redstone relay block.

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

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

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

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

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

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

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

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

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

Fixes #1957
2024-10-27 09:49:01 +00:00
Jonathan Coates
c271ed7c7f Fix wrong link in os.date docs
Should be os.time, not os.date! Fixes #1999
2024-10-24 14:03:06 +01:00
Karotte128
d6a246c122 Small german translation update (#1992) 2024-10-11 20:36:28 +02:00
Jonathan Coates
0bef3ee0d8 Install/run pre-commit directly
The update to Python 3.12 has broken the pre-commit action (as it
installs via pip rather than pipx). The maintainer seems unwilling to
fix it (to put it diplomatically), so let's just stop using the action
and imlement it ourselves.
2024-10-11 20:27:15 +02:00
Jonathan Coates
bb04df7086 Remove command translation keys
- <no pos> is no longer used, as positions cannot be nullable.
 - We don't really need the position one — we can just concatenate in
   Java.
2024-10-11 12:12:02 +02:00
RuyaSavascisi
a70baf0d74 Add Turkish translation 2024-09-28 15:03:33 +01:00
Jonathan Coates
86e2f92493 Add Turkish language mapping 2024-09-28 10:15:07 +01:00
Jonathan Coates
e9aceca1de Add a helper class for working with attached computers
One of the easiest things to mess up with writing a custom peripheral is
handling attached peripherals. IPeripheral.{attach,detach} are called
from multiple threads, so naive implementations that just store
computers in a set/list will at some point throw a CME.

Historically I've suggested using a concurrent collection (i.e.
ConcurrentHashMap). While this solves the problems of CMEs, it still has
some flaws. If a computer is detached while iterating over the
collection, the iterator will still yield the now-detached peripheral,
causing usages of that computer (e.g. queueEvent) to throw an exception.

The only fix here is to use a lock when updating and iterating over the
collection. This does come with some risks, but I think they are not too
serious:

 - Lock contention: Contention is relatively rare in general (as
   peripheral attach/detach is not especially frequent). If we do see
   contention, both iteration and update actions are cheap, so I would
   not expect the other thread to be blocked for a significant time.

 - Deadlocks: One could imagine an implementation if IComputerAccess
   that holds a lock both when detaching a peripheral and inside
   queueEvent.

   If we queue an event on one thread, and try to detach on the other,
   we could see a deadlock:

     Thread 1                         | Thread 2
    ----------------------------------------------------------
     AttachedComputerSet.queueEvent   | MyModem.detach
      (take lock #1)                  |  (take lock #2)

     -> MyModem.queueEvent            | AttachedComputerSet.remove
      (wait on lock #2)               |  (wait on lock #1)

   Such code would have been broken already (some peripherals already
   use locks), so I'm fairly sure we've fixed this in CC. But definitely
   something to watch out for.

Anyway, the long and short of it:
 - Add a new AttachedComputerSet that can be used to track the computers
   attached to a peripheral. We also mention this in the attach/detach
   docs, to hopefully make it a little more obvoius.

 - Update speakers and monitors to use this new class.
2024-09-22 13:51:11 +01:00
Jonathan Coates
3042950507 Update Cobalt to add math.atan(y, x) support 2024-09-22 13:13:07 +01:00
Jonathan Coates
f7a6aac657 Bump CC:T to 1.113.1 2024-09-11 18:27:13 +01:00
Jonathan Coates
782564e6ab Write colour channel as a single int
Should be a little more efficient than writing four bytes separately.
2024-09-11 18:27:13 +01:00
Jonathan Coates
6b8ba8b80b Shadow netty rather than jar-in-jaring
Under Forge, netty-codec lives on the BOOT layer. However, this means it
does not have access to our jzlib (which lives on the GAME layer). To
fix this, we now shadow netty-codec (and its dependents, like netty-http
and netty-proxy) rather than jar-in-jaring them.

This involves some horrible build logic, but means websocket compression
works on Forge.

Fixes #1958.
2024-09-11 18:00:12 +01:00
Jonathan Coates
52b76d8886 Some stability fixes to client tests
- Place the player above the test region before running tests. This
   guarantees the client has the chunks loaded (and rendered) before we
   start running tests.

 - Reset the time after running the monitor/printout tests.

 - Fix rotation of turtle item models.

This still isn't perfect - the first test still fails with Iris and
Sodium - but is an improvement. Probably will still fail in CI though
:D:.
2024-09-11 10:13:39 +01:00
Jonathan Coates
ba36c69583 Use ARGB32 to store palette colours
Previously we used an RGBA byte array. However, this comes with some
overhead (extra memory reads, bounds checks).

Minecraft 1.21+ uses ARGB32 colours for rendering (well, in the public
code — internaly it converts to ABGR), so it makes sense to match that
here.

We also add some helper functions for dealing with ARGB32 colours. These
can be removed in 1.21, as Minecraft will have these builtin.
2024-09-11 10:13:39 +01:00
Jonathan Coates
370e5f92a0 Wait until the turtle has gone in explosion tests
Rather than a fixed delay. This should help when turtle tasks are
running a little slow.
2024-09-11 08:52:42 +01:00
Jonathan Coates
36d05e4774 Some small optimisations to textutils.urlEncode
This probably isn't useful in practice — nobody is escaping 1MB of data.
Right. Right???? But no harm in doing it.

 - Cache globals as locals.
 - Remove redundant pattern capture.
 - Merge string.format calls into one.

Also remove the "if str then" check. I assume we accepted nil values a
long time ago, but that was broken when we added arg checks. Woops!
2024-08-30 10:15:24 +01:00
Jonathan Coates
89d1be17c9 Make common's CC:T dependencies api, not impl
While mods shouldn't be depending on common, sometimes it's unavoidable
(e.g. for cc-prometheus). In those cases, you want all the CC classes
available, not just the common ones.
2024-08-30 09:35:48 +01:00
Jonathan Coates
0069591af9 Fix overflow when converting recursive objects to Lua
In cdcd82679c, we rewrote the Lua
conversion function to update the "Java -> Lua" mapping after
conversion, rather than part way through.

This made the code a little cleaner (as we only updated the mapping in
one place), but is entirely incorrect — we need to store the object
first, in order to correctly handle recursive objects — otherwise we'll
just recurse infinitely (or until we overflow).

This partially reverts the above commit, while preserving the new
behaviour for singleton collections.

Fixes #1955.
2024-08-25 09:24:10 +01:00
Jonathan Coates
c36c8605bf Mention Crowdin in our documentation 2024-08-24 22:55:14 +01:00
Jonathan Coates
3c72a00d46 Write translations to the root 2024-08-24 18:46:22 +01:00
Jonathan Coates
58aefc8df8 Add Crowdin config
We may end up reverting this, but let's give it a go at least.
2024-08-24 18:29:17 +01:00
Konuma Takaki
97ddfc2794 Japanese translation update (#1950) 2024-08-22 09:43:55 +00:00
Jonathan Coates
6e4ec86586 Fix repeated words in DFPWM docs
Even more of these! I really need to stop getting bored half way through
writing sentences and then continue writing at the wrong place. Or,
y'know, get better at proof reading.
2024-08-20 09:22:04 +01:00
735 changed files with 12152 additions and 13198 deletions

View File

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

View File

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

View File

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

View File

@@ -30,27 +30,6 @@ jobs:
- name: ⚒️ Build
run: ./gradlew assemble || ./gradlew assemble
- name: 💡 Lint
uses: pre-commit/action@v3.0.0
- name: 🧪 Run tests
run: ./gradlew test validateMixinNames checkChangelog
- name: 📥 Download assets for game tests
run: ./gradlew downloadAssets || ./gradlew downloadAssets
- name: 🧪 Run integration tests
run: ./gradlew runGametest
- name: 🧪 Run client tests
run: ./gradlew runGametestClient # Not checkClient, as no point running rendering tests.
# These are a little flaky on GH actions: its useful to run them, but don't break the build.
continue-on-error: true
- name: 🧪 Parse test reports
run: ./tools/parse-reports.py
if: ${{ failure() }}
- name: 📦 Prepare Jars
run: |
# Find the main jar and append the git hash onto it.
@@ -63,8 +42,29 @@ jobs:
name: CC-Tweaked
path: ./jars
- name: 📤 Upload coverage
uses: codecov/codecov-action@v4
- name: Cache pre-commit
uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit-3|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml') }}
- name: 💡 Lint
run: |
pipx install pre-commit
pre-commit run --show-diff-on-failure --color=always
- name: 🧪 Run tests
run: ./gradlew test validateMixinNames checkChangelog
- name: 📥 Download assets for game tests
run: ./gradlew downloadAssets || ./gradlew downloadAssets
- name: 🧪 Run integration tests
run: ./gradlew runGametest
- name: 🧪 Parse test reports
run: ./tools/parse-reports.py
if: ${{ failure() }}
build-core:
strategy:

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

@@ -12,6 +12,7 @@ If you've any other questions, [just ask the community][community] or [open an i
## Table of Contents
- [Reporting issues](#reporting-issues)
- [Translations](#translations)
- [Setting up a development environment](#setting-up-a-development-environment)
- [Developing CC: Tweaked](#developing-cc-tweaked)
- [Writing documentation](#writing-documentation)
@@ -20,6 +21,9 @@ If you've any other questions, [just ask the community][community] or [open an i
If you have a bug, suggestion, or other feedback, the best thing to do is [file an issue][new-issue]. When doing so, do
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.
## Setting up a development environment
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
@@ -44,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.
@@ -81,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
@@ -102,3 +109,4 @@ about how you can build on that until you've covered everything!
[busted]: https://github.com/Olivine-Labs/busted "busted: Elegant Lua unit testing."
[node]: https://nodejs.org/en/ "Node.js"
[architecture]: projects/ARCHITECTURE.md
[Crowdin]: https://crowdin.com/project/cc-tweaked/

View File

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

View File

@@ -8,18 +8,23 @@ SPDX-PackageSupplier = "Jonathan Coates <git@squiddev.cc>"
SPDX-PackageDownloadLocation = "https://github.com/cc-tweaked/cc-tweaked"
[[annotations]]
# Generated/data files are CC0.
SPDX-FileCopyrightText = "The CC: Tweaked Developers"
SPDX-License-Identifier = "CC0-1.0"
path = [
# Generated/data files are CC0.
"gradle/gradle-daemon-jvm.properties",
"projects/common/src/main/resources/assets/computercraft/sounds.json",
"projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg",
"projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/**",
"projects/common/src/testMod/resources/data/cctest/structures/**",
"projects/**/src/generated/**",
"projects/*/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")
}
}
@@ -49,12 +41,10 @@ dependencies {
implementation(libs.kotlin.plugin)
implementation(libs.spotless)
implementation(libs.curseForgeGradle)
implementation(libs.fabric.loom)
implementation(libs.forgeGradle)
implementation(libs.ideaExt)
implementation(libs.librarian)
implementation(libs.minotaur)
implementation(libs.modDevGradle)
implementation(libs.vanillaExtract)
}
@@ -78,7 +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

@@ -30,7 +30,7 @@ repositories {
loom {
splitEnvironmentSourceSets()
splitModDependencies.set(true)
splitModDependencies = true
}
MinecraftConfigurations.setup(project)

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.legacyforge")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
legacyForge {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
version = "$mcVersion-${libs.findVersion("forge").get()}"
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,13 +44,6 @@ 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
@@ -91,14 +84,15 @@ sourceSets.all {
options.errorprone {
check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz
check("InvalidParam", CheckSeverity.OFF) // Broken by records.
check("InlineMeSuggester", CheckSeverity.OFF) // Minecraft uses @Deprecated liberally
// Too many false positives right now. Maybe we need an indirection for it later on.
check("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 +115,6 @@ tasks.compileTestJava {
}
}
tasks.withType(JavaCompile::class.java).configureEach {
options.encoding = "UTF-8"
}
@@ -169,8 +162,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 +187,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 +212,5 @@ idea.module {
// Force Gradle to write to inherit the output directory from the parent, instead of writing to out/xxx/classes.
// This is required for Loom, and we patch Forge's run configurations to work there.
// TODO: Submit a patch to Forge to support ProjectRootManager.
inheritOutputDirs = true
}

View File

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

View File

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

View File

@@ -2,44 +2,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,40 +223,31 @@ abstract class CCTweakedExtension(
).resolve().single()
}
/**
* Exclude a dependency from being published in Maven.
*/
fun exclude(dep: Dependency) {
excludedDeps.add(dep)
}
private fun <T> gitProvider(default: T, command: List<String>, process: (String) -> T): Provider<T> {
val baseResult = project.providers.exec {
commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command
}
/**
* Configure a [MavenDependencySpec].
*/
fun configureExcludes(spec: MavenDependencySpec) {
for (dep in excludedDeps.get()) spec.exclude(dep)
return project.provider {
val res = try {
baseResult.standardOutput.asText.get()
} catch (e: IOException) {
project.logger.error("Cannot read Git repository: ${e.message}", e)
return@provider default
} catch (e: GradleException) {
project.logger.error("Cannot read Git repository: ${e.message}", e)
return@provider default
}
process(res)
}
}
companion object {
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
private val IGNORED_USERS = setOf(
"GitHub", "Daniel Ratcliffe", "Weblate",
"GitHub", "Daniel Ratcliffe", "NotSquidDev", "Weblate",
)
private fun <T> gitProvider(project: Project, default: T, supplier: () -> T): Provider<T> {
return project.provider {
try {
supplier()
} catch (e: IOException) {
project.logger.error("Cannot read Git repository: ${e.message}")
default
} catch (e: GradleException) {
project.logger.error("Cannot read Git repository: ${e.message}")
default
}
}
}
private val isIdeSync: Boolean
get() = java.lang.Boolean.parseBoolean(System.getProperty("idea.sync.active", "false"))
}

View File

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

@@ -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
@@ -65,11 +65,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.
}

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

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

29
crowdin.yml Normal file
View File

@@ -0,0 +1,29 @@
# SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
#
# SPDX-License-Identifier: MPL-2.0
files:
- source: projects/common/src/generated/resources/assets/computercraft/lang/en_us.json
translation: /projects/common/src/main/resources/assets/computercraft/lang/%locale_with_underscore%.json
languages_mapping:
locale_with_underscore:
cs: cs_cz # Czech
da: da_dk # Danish
de: de_de # German
es-ES: es_es # Spanish
fr: fr_fr # French
hu: hu_hu # Hungarian
it: it_it # Italian
ja: ja_jp # Japanese
ko: ko_kr # Korean
nb: nb_no # Norwegian Bokmal
nl: nl_nl # Dutch
pl: pl_pl # Polish
pt-BR: pt_br # Portuguese, Brazilian
ru: ru_ru # Russian
sv-SE: sv_se # Sweedish
tok: tok # Toki Pona
tr: tr_tr # Turkish
uk: uk_ua # Ukraine
vi: vi_vn # Vietnamese
zh-CN: zh_cn # Chinese Simplified

View File

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

View File

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

View File

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

View File

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

View File

@@ -81,7 +81,7 @@ compatibility for these newer versions.
| `string.dump` strip argument | ✔ | |
| `string.pack`/`string.unpack`/`string.packsize` | ✔ | |
| `table.move` | ✔ | |
| `math.atan2` -> `math.atan` | | |
| `math.atan2` -> `math.atan` | 🔶 | `math.atan` supports its two argument form. |
| Removed `math.frexp`, `math.ldexp`, `math.pow`, `math.cosh`, `math.sinh`, `math.tanh` | ❌ | |
| `math.maxinteger`/`math.mininteger` | ❌ | |
| `math.tointeger` | ❌ | |

View File

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

View File

@@ -26,14 +26,14 @@ slf4j = "2.0.1"
asm = "9.6"
autoService = "1.1.1"
checkerFramework = "3.42.0"
cobalt = "0.9.3"
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"
@@ -46,35 +46,35 @@ oculus = "1.2.5"
rei = "12.0.626"
rubidium = "0.6.1"
sodium = "mc1.20-0.4.10"
create-forge = "0.5.1.f-33"
create-forge = "6.0.0-9"
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
# Testing
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.2"
checkstyle = "10.14.1"
curseForgeGradle = "1.0.14"
errorProne-core = "2.27.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.7.1"
forgeGradle = "6.0.21"
cctJavadoc = "1.8.4"
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-73-g43ee16c"
librarian = "1.+"
illuaminate = "0.1.0-83-g1131f68"
lwjgl = "3.3.3"
minotaur = "2.+"
nullAway = "0.10.25"
spotless = "6.23.3"
minotaur = "2.8.7"
modDevGradle = "2.0.95"
nullAway = "0.12.7"
shadow = "8.3.1"
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]
@@ -89,14 +89,15 @@ fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" }
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
jzlib = { module = "com.jcraft:jzlib", version.ref = "jzlib" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
netty-codec = { module = "io.netty:netty-codec", version.ref = "netty" }
netty-http = { module = "io.netty:netty-codec-http", version.ref = "netty" }
netty-socks = { module = "io.netty:netty-codec-socks", version.ref = "netty" }
netty-proxy = { module = "io.netty:netty-handler-proxy", version.ref = "netty" }
netty-socks = { module = "io.netty:netty-codec-socks", version.ref = "netty" }
nightConfig-core = { module = "com.electronwill.night-config:core", version.ref = "nightConfig" }
nightConfig-toml = { module = "com.electronwill.night-config:toml", version.ref = "nightConfig" }
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
@@ -130,6 +131,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" }
@@ -143,19 +145,17 @@ lwjgl-glfw = { module = "org.lwjgl:lwjgl-glfw" }
# Build tools
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
curseForgeGradle = { module = "net.darkhax.curseforgegradle:CurseForgeGradle", version.ref = "curseForgeGradle" }
errorProne-annotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "errorProne-core" }
errorProne-api = { module = "com.google.errorprone:error_prone_check_api", version.ref = "errorProne-core" }
errorProne-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorProne-core" }
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
modDevGradle = { module = "net.neoforged:moddev-gradle", version.ref = "modDevGradle" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
teavm-core = { module = "org.teavm:teavm-core", version.ref = "teavm" }
@@ -170,16 +170,15 @@ 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
@@ -191,7 +190,7 @@ externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
# Testing
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]
testRuntime = ["junit-jupiter-engine", "junit-platform-launcher", "jqwik-engine"]
# Build tools
teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"]

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-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

1611
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-typescript": "^11.0.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-typescript": "^12.0.0",
"@rollup/plugin-url": "^8.0.1",
"@swc/core": "^1.3.92",
"@types/node": "^20.8.3",
"@types/node": "^24.0.0",
"lightningcss": "^1.22.0",
"preact-render-to-string": "^6.2.1",
"rehype": "^13.0.0",

View File

@@ -8,6 +8,8 @@ plugins {
id("cc-tweaked.vanilla")
}
val mcVersion: String by extra
java {
withJavadocJar()
}
@@ -16,9 +18,61 @@ dependencies {
api(project(":core-api"))
}
val javadocOverview by tasks.registering(Copy::class) {
from("src/overview.html")
into(layout.buildDirectory.dir(name))
expand(
mapOf(
"mcVersion" to mcVersion,
"modVersion" to version,
),
)
}
tasks.javadoc {
title = "CC: Tweaked $version for Minecraft $mcVersion"
include("dan200/computercraft/api/**/*.java")
options {
(this as StandardJavadocDocletOptions)
inputs.files(javadocOverview)
overview(javadocOverview.get().destinationDir.resolve("overview.html").absolutePath)
groups = mapOf(
"Common" to listOf(
"dan200.computercraft.api",
"dan200.computercraft.api.lua",
"dan200.computercraft.api.peripheral",
),
"Upgrades" to listOf(
"dan200.computercraft.api.client.turtle",
"dan200.computercraft.api.pocket",
"dan200.computercraft.api.turtle",
"dan200.computercraft.api.upgrades",
),
)
addBooleanOption("-allow-script-in-comments", true)
bottom(
"""
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/components/prism-core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
<link href=" https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css " rel="stylesheet">
""".trimIndent(),
)
taglets("cc.tweaked.javadoc.SnippetTaglet")
tagletPath(configurations.detachedConfiguration(dependencies.project(":lints")).toList())
val snippetSources = listOf(":common", ":fabric", ":forge").flatMap {
project(it).sourceSets["examples"].allSource.sourceDirectories
}
inputs.files(snippetSources)
jFlags("-Dcc.snippet-path=" + snippetSources.joinToString(File.pathSeparator) { it.absolutePath })
}
// 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 })
}

View File

@@ -12,8 +12,8 @@ import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
@@ -22,7 +22,14 @@ import java.util.List;
* <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
* on Forge.
*
* <h2>Example</h2>
* <h3>Fabric</h3>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
* <h3>Forge</h3>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
* @param <T> The type of turtle upgrade this modeller applies to.
* @see RegisterTurtleUpgradeModeller For multi-loader registration support.

View File

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

View File

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

View File

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

View File

@@ -26,8 +26,7 @@ import net.minecraft.core.Direction;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* The static entry point to the ComputerCraft API.
@@ -171,16 +170,9 @@ public final class ComputerCraftAPI {
* using {@link ILuaAPI#getModuleName()} to expose this library as a module instead of as a global.
* <p>
* This may be used with {@link IComputerSystem#getComponent(ComputerComponent)} to only attach APIs to specific
* computers. For example, one can add an additional API just to turtles with the following code:
* computers. For example, one can add a new API just to turtles with the following code:
*
* <pre>{@code
* ComputerCraftAPI.registerAPIFactory(computer -> {
* // Read the turtle component.
* var turtle = computer.getComponent(ComputerComponents.TURTLE);
* // If present then add our API.
* return turtle == null ? null : new MyCustomTurtleApi(turtle);
* });
* }</pre>
* {@snippet class=com.example.examplemod.ExampleAPI region=register}
*
* @param factory The factory for your API subclass.
* @see ILuaAPIFactory

View File

@@ -26,6 +26,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()}.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,20 +7,16 @@ package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* A peripheral which can be equipped to the back side of a pocket computer.
* <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.
* {@link PocketUpgradeSerialiser} 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 {
/**

View File

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

View File

@@ -8,21 +8,69 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.Items;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.function.BiFunction;
/**
* 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 TurtleUpgradeSerialiser} 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 serialiser</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.
* <p>
* {@snippet class=com.example.examplemod.ExampleTurtleUpgrade region=body}
* <p>
* Now we must construct a new upgrade serialiser. In most cases, you can use one of the helper methods
* (e.g. {@link TurtleUpgradeSerialiser#simpleWithCustomItem(BiFunction)}), rather than defining your own implementation.
*
* {@snippet class=com.example.examplemod.ExampleMod region=turtle_upgrades}
*
* We now must register this upgrade serialiser. 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>Rendering the upgrade</h3>
* Next, we need to register a model for our upgrade. This is done by registering a
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for your upgrade serialiser.
*
* <h4>Fabric</h4>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
*
* <h4>Forge</h4>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
* <h3>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_upgrades/<my_upgrade_id>.json}.
*
* {@snippet file = data/examplemod/computercraft/turtle_upgrades/example_turtle_upgrade.json}
*
* The {@code "type"} field points to the ID of the upgrade serialiser we've just registered, while the other fields
* are read by the serialiser itself. As our upgrade was defined with {@link TurtleUpgradeSerialiser#simpleWithCustomItem(BiFunction)}, the
* {@code "item"} field will construct our upgrade with {@link Items#COMPASS}.
* <p>
* Rather than manually creating the file, it is recommended to data-generators to generate this file. This can be done
* with {@link TurtleUpgradeDataProvider}.
*
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
*
* @see TurtleUpgradeSerialiser Registering a turtle upgrade.
*/
public interface ITurtleUpgrade extends UpgradeBase {
/**

View File

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

View File

@@ -18,8 +18,8 @@ import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.function.Consumer;
/**
@@ -29,6 +29,9 @@ import java.util.function.Consumer;
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
* generate them.
*
* <h2>Example</h2>
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
*
* @see TurtleUpgradeSerialiser
*/
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {

View File

@@ -27,32 +27,6 @@ import java.util.function.Function;
* If your turtle upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
* {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
*
* <h2>Example (Forge)</h2>
* <pre>{@code
* static final DeferredRegister<TurtleUpgradeSerialiser<?>> SERIALISERS = DeferredRegister.create( TurtleUpgradeSerialiser.TYPE, "my_mod" );
*
* // Register a new upgrade serialiser called "my_upgrade".
* public static final RegistryObject<TurtleUpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
* SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
*
* // Then in your constructor
* SERIALISERS.register( bus );
* }</pre>
* <p>
* We can then define a new upgrade using JSON by placing the following in
* {@literal data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}}.
*
* <pre>{@code
* {
* "type": my_mod:my_upgrade",
* }
* }</pre>
* <p>
* Finally, we need to register a model for our upgrade. 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

View File

@@ -9,8 +9,7 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.

View File

@@ -19,8 +19,8 @@ import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -32,6 +32,8 @@ import java.util.function.Function;
*
* @param <T> The base class of upgrades.
* @param <R> The upgrade serialiser to register for.
* @see dan200.computercraft.api.turtle.TurtleUpgradeDataProvider
* @see dan200.computercraft.api.pocket.PocketUpgradeDataProvider
*/
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
private final PackOutput output;
@@ -84,13 +86,9 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
/**
* Add all turtle or pocket computer upgrades.
* <p>
* <strong>Example usage:</strong>
* <pre>{@code
* protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
* simple(new ResourceLocation("mymod", "speaker"), SPEAKER_SERIALISER.get()).add(addUpgrade);
* }
* }</pre>
*
* <h4>Example</h4>
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
*
* @param addUpgrade A callback used to register an upgrade.
*/

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,68 @@
<!--
SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
<!DOCTYPE HTML>
<html lang="en">
<body>
<p>
This is the documentation for CC: Tweaked $modVersion for Minecraft $mcVersion. Documentation for other versions of
Minecraft are available on the CC: Tweaked website:
<ul>
<li><a href="/mc-1.20.x/javadoc/">Minecraft 1.20.1</a>
<li><a href="/mc-1.21.x/javadoc/">Minecraft 1.21.1</a>
</ul>
<h1>Quick links</h1>
<p>
You probably want to start in the following places:
<ul>
<li>{@linkplain dan200.computercraft.api.peripheral Registering new peripherals}</li>
<li>
{@link dan200.computercraft.api.lua.LuaFunction} and {@link dan200.computercraft.api.lua.IArguments} for
adding methods to your peripheral or Lua objects.
</li>
<li>{@linkplain dan200.computercraft.api.turtle.ITurtleUpgrade Turtle upgrades}</li>
<li>{@linkplain dan200.computercraft.api.pocket.IPocketUpgrade Pocket upgrades}</li>
</ul>
<h1>Using</h1>
<p>
CC: Tweaked is hosted on my maven repo, and so is relatively simple to depend on. You may wish to add a soft (or
hard) dependency in your <code>mods.toml</code> file, with the appropriate version bounds, to ensure that API
functionality you depend on is present.
<pre class="language language-groovy"><code>repositories {
maven {
url "https://maven.squiddev.cc"
content { includeGroup("cc.tweaked") }
}
}
dependencies {
// Vanilla (i.e. for multi-loader systems)
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$modVersion")
// Forge Gradle
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$modVersion")
compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$modVersion"))
runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$modVersion"))
// Fabric Loom
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$modVersion")
modRuntimeOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric:$modVersion")
}
</code></pre>
<p>
You should also be careful to only use classes within the <code>dan200.computercraft.api</code> 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 <a href="https://github.com/cc-tweaked/CC-Tweaked/discussions/new/choose">start a discussion</a> to
let me know!
</body>
</html>

View File

@@ -6,17 +6,11 @@ import cc.tweaked.gradle.*
plugins {
id("cc-tweaked.vanilla")
id("cc-tweaked.gametest")
id("cc-tweaked.illuaminate")
id("cc-tweaked.mod")
id("cc-tweaked.publishing")
}
sourceSets {
main {
resources.srcDir("src/generated/resources")
}
}
minecraft {
accessWideners(
"src/main/resources/computercraft.accesswidener",
@@ -38,12 +32,11 @@ repositories {
dependencies {
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
implementation(project(":core"))
implementation(commonClasses(project(":common-api")))
clientImplementation(clientClasses(project(":common-api")))
api(project(":core"))
api(commonClasses(project(":common-api")))
clientApi(clientClasses(project(":common-api")))
compileOnly(libs.bundles.externalMods.common)
compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false }
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
annotationProcessorEverywhere(libs.autoService)
@@ -67,7 +60,7 @@ dependencies {
}
illuaminate {
version.set(libs.versions.illuaminate)
version = libs.versions.illuaminate
}
val luaJavadoc by tasks.registering(Javadoc::class) {
@@ -88,11 +81,7 @@ val luaJavadoc by tasks.registering(Javadoc::class) {
options.addStringOption("project-root", rootProject.file(".").absolutePath)
options.noTimestamp(false)
javadocTool.set(
javaToolchains.javadocToolFor {
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
},
)
javadocTool = javaToolchains.javadocToolFor { languageVersion = CCTweakedPlugin.JAVA_VERSION }
}
val lintLua by tasks.registering(IlluaminateExec::class) {
@@ -113,20 +102,31 @@ val lintLua by tasks.registering(IlluaminateExec::class) {
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}
val runData by tasks.registering(MergeTrees::class) {
output = layout.projectDirectory.dir("src/generated/resources")
fun MergeTrees.configureForDatagen(source: SourceSet, outputFolder: String) {
output = layout.projectDirectory.dir(outputFolder)
for (loader in listOf("forge", "fabric")) {
mustRunAfter(":$loader:runData")
mustRunAfter(":$loader:$name")
source {
input {
from(project(":$loader").layout.buildDirectory.dir("generatedResources"))
from(project(":$loader").layout.buildDirectory.dir(source.getTaskName("generateResources", null)))
exclude(".cache")
}
output = project(":$loader").layout.projectDirectory.dir("src/generated/resources")
output = project(":$loader").layout.projectDirectory.dir(outputFolder)
}
}
}
tasks.withType(GenerateModuleMetadata::class).configureEach { isEnabled = false }
val runData by tasks.registering(MergeTrees::class) {
configureForDatagen(sourceSets.main.get(), "src/generated/resources")
}
val runExampleData by tasks.registering(MergeTrees::class) {
configureForDatagen(sourceSets.examples.get(), "src/examples/generatedResources")
}
// We can't create accurate module metadata for our additional capabilities, so disable it.
project.tasks.withType(GenerateModuleMetadata::class.java).configureEach {
isEnabled = false
}

View File

@@ -38,8 +38,8 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.function.Consumer;
/**
@@ -64,6 +64,9 @@ public final class ClientHooks {
public static void onWorldUnload() {
MonitorRenderState.destroyAll();
SpeakerManager.reset();
}
public static void onDisconnect() {
ClientPocketComputers.reset();
}

View File

@@ -46,8 +46,10 @@ import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.function.BiConsumer;
@@ -63,6 +65,8 @@ import java.util.function.Supplier;
* @see ModRegistry The common registry for actual game objects.
*/
public final class ClientRegistry {
private static final Logger LOG = LoggerFactory.getLogger(ClientRegistry.class);
private ClientRegistry() {
}
@@ -182,7 +186,18 @@ public final class ClientRegistry {
}
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
RenderTypes.registerShaders(resources, load);
RenderTypes.registerShaders(resources, (name, create, onLoaded) -> {
ShaderInstance shader;
try {
shader = create.get();
} catch (Exception e) {
LOG.error("Failed to load {}", name, e);
onLoaded.accept(null);
return;
}
load.accept(shader, onLoaded);
});
}
private record UnclampedPropertyFunction(

View File

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

View File

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

View File

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

View File

@@ -6,14 +6,13 @@ package dan200.computercraft.client.gui;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.render.ComputerBorderRenderer;
import dan200.computercraft.data.client.ClientDataProviders;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.TextureAtlasHolder;
import net.minecraft.resources.ResourceLocation;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.stream.Stream;
@@ -33,10 +32,13 @@ public final class GuiSprites extends TextureAtlasHolder {
public static final ComputerTextures COMPUTER_COMMAND = computer("command", false, true);
public static final ComputerTextures COMPUTER_COLOUR = computer("colour", true, false);
public static final ResourceLocation TURTLE_NORMAL_SELECTED_SLOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/turtle_normal_selected_slot");
public static final ResourceLocation TURTLE_ADVANCED_SELECTED_SLOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/turtle_advanced_selected_slot");
private static ButtonTextures button(String name) {
return new ButtonTextures(
new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name + "_hover")
new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/buttons/" + name),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/buttons/" + name + "_hover")
);
}
@@ -113,7 +115,6 @@ public final class GuiSprites extends TextureAtlasHolder {
* @param pocketBottom The texture for the bottom of a pocket computer.
* @param sidebar The texture for the computer sidebar.
* @see ComputerBorderRenderer
* @see ClientDataProviders
*/
public record ComputerTextures(
ResourceLocation border,

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.gui;
import org.lwjgl.glfw.GLFW;
/**
* Supports for converting/translating key codes.
*/
public class KeyConverter {
/**
* GLFW's key events refer to the physical key code, rather than the "actual" key code (with keyboard layout
* applied).
* <p>
* This makes sense for WASD-style input, but is a right pain for keyboard shortcuts this function attempts to
* translate those keys back to their "actual" key code. See also
* <a href="https://github.com/glfw/glfw/issues/1502"> this discussion on GLFW's GitHub.</a>
*
* @param key The current key code.
* @param scanCode The current scan code.
* @return The translated key code.
*/
public static int physicalToActual(int key, int scanCode) {
var name = GLFW.glfwGetKeyName(key, scanCode);
if (name == null || name.length() != 1) return key;
// If we've got a single character as the key name, treat that as the ASCII value of the key,
// and map that back to a key code.
var character = name.charAt(0);
// 0-9 and A-Z map directly to their GLFW key (they're the same ASCII code).
if ((character >= '0' && character <= '9') || (character >= 'A' && character <= 'Z')) return character;
// a-z map to GLFW_KEY_{A,Z}
if (character >= 'a' && character <= 'z') return GLFW.GLFW_KEY_A + (character - 'a');
return key;
}
}

View File

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

View File

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

View File

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

View File

@@ -54,9 +54,9 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
if (slot >= 0) {
var slotX = slot % 4;
var slotY = slot / 4;
graphics.blit(texture,
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0,
0, 217, 24, 24, FULL_TEX_SIZE, FULL_TEX_SIZE
graphics.blit(
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0, 22, 22,
GuiSprites.get(advanced ? GuiSprites.TURTLE_ADVANCED_SELECTED_SLOT : GuiSprites.TURTLE_NORMAL_SELECTED_SLOT)
);
}

View File

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

View File

@@ -12,8 +12,8 @@ import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.network.chat.Component;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.function.Supplier;
/**

View File

@@ -5,6 +5,7 @@
package dan200.computercraft.client.gui.widgets;
import com.mojang.blaze3d.vertex.Tesselator;
import dan200.computercraft.client.gui.KeyConverter;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
@@ -71,11 +72,8 @@ public class TerminalWidget extends AbstractWidget {
@Override
public boolean charTyped(char ch, int modifiers) {
if (ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255) {
// Queue the char event for any printable chars in byte range
computer.queueEvent("char", new Object[]{ Character.toString(ch) });
}
var terminalChar = StringUtil.unicodeToTerminal(ch);
if (StringUtil.isTypableChar(terminalChar)) computer.charTyped((byte) terminalChar);
return true;
}
@@ -88,7 +86,7 @@ public class TerminalWidget extends AbstractWidget {
}
if ((modifiers & GLFW.GLFW_MOD_CONTROL) != 0) {
switch (key) {
switch (KeyConverter.physicalToActual(key, scancode)) {
case GLFW.GLFW_KEY_T -> {
if (terminateTimer < 0) terminateTimer = 0;
}
@@ -112,8 +110,8 @@ public class TerminalWidget extends AbstractWidget {
}
private void paste() {
var clipboard = StringUtil.normaliseClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
if (!clipboard.isEmpty()) computer.queueEvent("paste", new Object[]{ clipboard });
var clipboard = StringUtil.getClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
if (clipboard.remaining() > 0) computer.paste(clipboard);
}
@Override
@@ -124,7 +122,7 @@ public class TerminalWidget extends AbstractWidget {
computer.keyUp(key);
}
switch (key) {
switch (KeyConverter.physicalToActual(key, scancode)) {
case GLFW.GLFW_KEY_T -> terminateTimer = -1;
case GLFW.GLFW_KEY_R -> rebootTimer = -1;
case GLFW.GLFW_KEY_S -> shutdownTimer = -1;
@@ -222,7 +220,7 @@ public class TerminalWidget extends AbstractWidget {
public void update() {
if (terminateTimer >= 0 && terminateTimer < TERMINATE_TIME && (terminateTimer += 0.05f) > TERMINATE_TIME) {
computer.queueEvent("terminate");
computer.terminate();
}
if (shutdownTimer >= 0 && shutdownTimer < TERMINATE_TIME && (shutdownTimer += 0.05f) > TERMINATE_TIME) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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