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

Compare commits

...

150 Commits

Author SHA1 Message Date
Jonathan Coates
6a116aadb8 Bump CC:T to 1.103.1
Woops!

 - Fix the REPL not printing values, as exception.try didn't return
   values. It did originally, and then I tried to simplify it >_>

 - Change repl_exprs to run an expression and program parser in
   parallel, rather than handling the parallelism on the grammar side -
   that has a few shift/reduce conflicts which result in bad parse
   errors.
2023-02-09 21:45:20 +00:00
Jonathan Coates
9e6e0c8b88 Bump CC:T to 1.103.0 2023-02-09 21:02:09 +00:00
Jonathan Coates
5502412181 Improve display of runtime errors (#1320)
- Bump Cobalt to 0.6.0. We now track both line and column numbers of
   each bytecode instruction, allowing us to map an error to a concrete
   position.

 - `loadfile` (and similar functions) now use the full path, rather than
   the file name. Cobalt truncates this to 30 characters (rather than
   the previous 60) so this should be less noisy.

 - The shell, edit and Lua REPL now display the corresponding source
   code alongside an error.

   Note this is incredibly limited right now - it won't cope with errors
   which cross coroutine boundaries. Supporting this is on the roadmap,
   but requires some careful API design.
2023-02-09 20:53:50 +00:00
Jonathan Coates
62e3c5f9aa Queue key presses for a short period after pressing a computer shortcut
Fixes #1326
2023-02-09 20:10:20 +00:00
Jonathan Coates
7e54a40fa9 Add a couple of missing version annotations 2023-02-09 20:09:57 +00:00
Jonathan Coates
8ac42566ec Be a little more robust in turtle item rendering
See #1328. Also some holiday cleanup, because my commit discipline is
terrible.
2023-02-09 20:07:55 +00:00
Jonathan Coates
66b20d2bdb Don't dye/undye turtles when right clicked
While it's a nice immersive interaction, it's far too easy to trigger by
accident. Dying/undying can now only be done via crafting and the
cauldron.

Closes #1321
2023-02-08 20:14:07 +00:00
Jonathan Coates
81dad421d5 Allow undying items using a sponge
It's much less aggressive than dunking it in a cauldron, so won't damage
any of your precious electronics.

I had this idea back in 2017 (dan200/ComputerCraft#230). Can't believe
it took me almost 6 years to implement.
2023-02-08 20:13:58 +00:00
Jonathan Coates
3075d3cea8 Fxi teh spolling of handshakder 2023-02-07 20:59:45 +00:00
Jonathan Coates
cdab8f429e Clarify Minecraft' shift+right click behaviour
Closes #1323
2023-02-07 08:45:17 +00:00
Jonathan Coates
2e5cd29e12 Use tags to what items turtle.place() can .use()
In older versions we just used a hard-coded list of items and
superclasses. This was somewhat ugly, and so in 1.19.3 I tried to make
this code more generic.

However, this has a lot of unintended consequences - for instance
turtles can now throw ender pearls, which is definitely not intended!

By using a tag, we can emulate the old behaviour, while still allowing
modders and pack devs to add additional items if needed.
2023-02-04 18:14:18 +00:00
Jonathan Coates
22cadd6730 Various improvements to our Lua parser
- Encode the DFA as a virtual machine (identical to lrgrep) rather than
   compiling it to a series of Lua functions. While this is a little
   slower and uglier, it's much more space efficient, shaving off 16Kb.

 - Minimise the DFA properly. This only shaves off a few states, but
   every little helps.

 - Run the error handling code from a non-reduced parser stack. This was
   incredibly nasty to get right (and positions are still not correctly
   handled), but it fixes several broken error messages.
2023-02-04 12:46:51 +00:00
Jonathan Coates
366052ec48 Merge pull request #1316 from LelouBil/patch-1
Fix word error in rednet documentation
2023-02-03 19:39:13 +00:00
Jonathan Coates
3224e0bf8b Fix a few typos in the documentation
- Move modem recipes out of the usage section.
 - Add missing argument names to BinaryWriableHandle.write. Illuaminate
   really should catch this, but for now I did a grep and couldn't find
   any more instances of this.
2023-02-03 19:29:16 +00:00
Weblate
fb4b097a66 Translations for Toki Pona
Co-authored-by: wackbyte <wackbyte@protonmail.com>
2023-02-03 04:55:48 +00:00
Bilel MEDIMEGH
67f3d91850 Fix word error in rednet documentation
Replaced "The name this protocol exposes for the given protocol" into "The name this **computer** exposes for the given protocol"
2023-01-30 18:19:06 +01:00
Jonathan Coates
1e3a930543 Copy pocket upgrades items before dropping them
We were modifying the stack, meaning the crafting item would end up
empty!
2023-01-26 22:40:03 +00:00
Jonathan Coates
b21e2f4e63 Update Cobalt to fix yielding inside __len
Closes #1307
2023-01-26 09:43:10 +00:00
Jonathan Coates
a12b405acf Custom parse errors for Lua (#1298)
- Add several (internal) modules for lexing and parsing Lua code. These
   allow us to provide (hopefully) higher quality error messages than
   Lua's built-in messages.

 - `shell.run`, `edit` and `lua` now use this parser when fed invalid
   code. This allows us to provide better syntax errors, while not
   having any impact on the happy path.

   Note this does not affect any other mechanism for loading code 
   (`load`, `require`, `dofile`).

There's still a lot of work to do here in improving error message
quality, but hopefully this provides a good starting point.
2023-01-25 20:35:43 +00:00
Jonathan Coates
e076818b29 Bump Cobalt for even more verbose VM logging
See #1307
2023-01-25 18:47:27 +00:00
Jonathan Coates
1554c7b397 Always expose nbt from turtle.getItemDetail
- Document the thread safety of DetailRegistry a little better.

 - Turtles now duplicate their inventory to the "previous
   inventory" (now called inventorySnapshot) immediately, rather than
   when the block is ticked.

   This is slightly more resource intensive, but I don't think it's so
   bad we need to worry.

 - As this snapshot is now always up-to-date, we can read it from the
   computer thread. Given the item is immutable, it's safe to read NBT
   from it.

   _Technically_ this is not safe under the Java memory model, but in
   practice I don't think we'll observe the wrong value.

Closes #1306
2023-01-25 18:37:14 +00:00
Jonathan Coates
6cd32a6368 Fix a few typos
Just ran[^1] over the codebase. Turns out we'd duplicated one of the
changelog entries entirely - I suspect due to a version merge gone
wrong!

[^1]: https://github.com/crate-ci/typos/
2023-01-24 18:47:29 +00:00
Jonathan Coates
7335a892b5 Expose test methods to required libraries
This allows us to use stub everywhere, rather than having to pass it
around as an argument.
2023-01-24 18:27:30 +00:00
Weblate
83eddc6636 Translations for Toki Pona
Translations for Italian

Co-authored-by: Alessandro <ale.proto00@gmail.com>
Co-authored-by: wackbyte <wackbyte@protonmail.com>
2023-01-24 05:55:48 +00:00
Jonathan Coates
d066d175bf Attach a speaker peripheral inside its docs
This required a version bump to cct-javadoc to allow using attributes
inside <pre> blocks.
2023-01-22 12:24:36 +00:00
Jonathan Coates
9873ccfa0d Send the original rednet message to the current computer
We were incorrectly enquing the modem payload, not the underlying rednet
message.

Closes #1308.
2023-01-21 08:23:00 +00:00
Weblate
da7a50368d Added translation for Toki Pona
Co-authored-by: wackbyte <wackbyte@protonmail.com>
2023-01-18 14:59:35 +00:00
Jonathan Coates
8cfbfe7ceb Make errors messages in edit more obvious
See #1222.
2023-01-17 21:43:26 +00:00
ouroborus
67244b17af Update event docs (#1304) 2023-01-17 21:17:47 +00:00
Jonathan Coates
9e1de23f4a Log internal Cobalt VM errors
See #1307
2023-01-17 20:37:16 +00:00
Jonathan Coates
86b60855d6 Enforce version bounds for the Kotlin stdlib
We were pulling in an ancient version of the jdk8 stdlib via
kotlinx.coroutines, hence the conflict in versions.
2023-01-17 20:28:31 +00:00
Jonathan Coates
db6b6fd173 Bump Kotlin version to 1.8 2023-01-17 19:33:49 +00:00
Jonathan Coates
2014e9527e Some event typo fixes 2023-01-16 22:53:58 +00:00
Emma
f43b839056 Add support for shebangs (#1273) 2023-01-14 22:12:04 +00:00
Jonathan Coates
f561572509 Bump CC:T to 1.102.2 2023-01-14 20:07:01 +00:00
Jonathan Coates
edb21f33be Mount drives on the main thread
When the peripheral is attached, we add the computer to the map and
queue the actual disk to be mounted next tick. This avoids the
thread-safety issues with mutating the item (and creating disk ids) that
might be caused by doing it on the computer thread.

The mount is now also managed separately to the MediaStack, as that was
meant to be an immutable snapshot of the item!

Fixes #1282
2023-01-14 17:51:49 +00:00
Jonathan Coates
02b68b259e Correctly track coverage for startup.lua too 2023-01-12 22:26:39 +00:00
Jonathan Coates
28a55349a9 Move coverage to the Java side
While slightly irritating (requires Cobalt magic), it's much, much
faster.
2023-01-12 21:02:33 +00:00
Jonathan Coates
2457a31728 Fix printouts crashing in item frames 2023-01-09 18:28:44 +00:00
Jonathan Coates
cdc91a8e5d Fix stack overflow in logging code 2023-01-08 18:29:36 +00:00
Jonathan Coates
8024017f53 Bump CC:T to 1.102.1
There's still some remaining bugs (#1282), but I think worth getting the
fixes for the worst issues out first.
2023-01-08 18:21:10 +00:00
Jonathan Coates
592ff84aea Read computer threads directly from the config object (#1295)
This gives us slightly better guarantees that the config has actually
been loaded. This, along with a FCAP bump, fixes this config option
not doing anything on Fabric.
2023-01-07 22:16:06 +00:00
Jonathan Coates
4360458416 Attempt to reduce test flakiness
I'm really not sure why the modem one fails. I can't reproduce outside
of CI, so quite hard to debug :/.
2023-01-07 15:05:21 +00:00
Jonathan Coates
717e096b94 Include the licences of our dependencies in the credits
I feel like we should have been doing this from the beginning. Love to
uncompliant for 11 years :/.
2023-01-06 09:34:07 +00:00
Jonathan Coates
34a31abd9c Move our internal module into the main package path
I originally put cc.import in a separate directory from the main
modules. This means that programs must extend the package path in order
to import these modules.

However, this ends up being a mixed blessing: while it makes it much
harder for users to accidentally require user code, it also means we
can't expose a public interface which wraps a private module.

Instead, cc.import now lives on the main package path, but lives under
the cc.internal namespace and is not documented anywhere. Hopefully this
should be enough of a clue that one shouldn't use it :p.
2023-01-05 21:55:08 +00:00
Jonathan Coates
bdecb88cca Support resource conditions in upgrade JSON 2023-01-02 15:56:01 +00:00
Weblate
af15030fa4 Translations for Italian
Co-authored-by: Trenord <luca.ilari@gmail.com>
2023-01-01 20:55:45 +00:00
Jonathan Coates
3a883db49e Test that wired modems form networks
See #1278
2023-01-01 17:57:53 +00:00
Jonathan Coates
8ea5b64f64 Improve our CI artifacts
- Publish Forge and Fabric
 - Include version hash in the download
2023-01-01 15:04:25 +00:00
Jonathan Coates
7b6caf76e4 Increase the distance from which we drop items
I was having issues where dropped items would clip into blocks when
dropped, and then phase upwards through the turtle instead. This makes
things a little more consistent with dispenser behaviour.
2023-01-01 14:51:47 +00:00
Jonathan Coates
230c7ee904 Clamp speaker volume again
Looks like this was removed in b048b6666d.
2023-01-01 08:51:07 +00:00
Jonathan Coates
aa203802c6 Fix search tab showing up in itemGroups 2022-12-31 16:24:19 +00:00
Jonathan Coates
1259e29f21 Fix full-block wired modems not connecting
Closes #1278.
2022-12-29 22:30:09 +00:00
Jonathan Coates
77f62dac94 Fix mouse_up not being fired on Fabric 2022-12-29 22:16:36 +00:00
Jonathan Coates
7f34aff6bb Correctly handle double chests on Fabric
Closes #1279. The perils of ignoring the transfer API :(.
2022-12-29 21:48:59 +00:00
Jonathan Coates
3047e3cdf4 Simplify cable/modem block breaking code
Instead of taking control of the breaking logic in all cases, we now
only do so when we have both a cable and modem. This allows us to fall
back to default vanilla behaviour and so correctly drop the modem/cable
item.
2022-12-29 12:21:10 +00:00
Emma
7a83a403f0 Fix OOB when item insertion wraps around (#1277)
Co-authored-by: Jonathan Coates <git@squiddev.cc>
2022-12-29 11:26:25 +00:00
Jonathan Coates
a1d5c76d00 Test for moving into a rotated container
Similar to the test in #1277. The duplication here is a little
irritating, but don't think we can avoid that.
2022-12-29 11:11:26 +00:00
Emma
bcdfa7c5ff Fix bug where turtle drops inventory when moving (#1276) 2022-12-29 09:02:02 +00:00
Jonathan Coates
2c59b9122b Set location when creating a pocket modem
We now call getLevel() when attaching the peripheral, so need the
position to be available immediately. Fixes #1274.

I /think/ the entity should always be present, as peripherals are
only created on startup or when calling pocket.equipBack, both of which
require a player.[^1]

I suspect this was a little broken before (the level wouldn't be
available if a modem received a message before the position had
been set), but that would be much rarer.

I'm not 100% convinced about the thread-safety of this code (the writes
to level may not be immediately visible on other threads), so may need
to think about that.

[^1]: Note that when peripherals come to be /attached/ they may no
longer have a player (as there's a gap between turning a computer on and
it actually starting). However, the level/position will have been
initialised by then, so this isn't a problem.
2022-12-29 08:52:28 +00:00
Emma
d2c7b944ab Fix crash on Fabric when attempting to use a non-fuel item as fuel. (#1275)
Co-authored-by: Jonathan Coates <git@squiddev.cc>
2022-12-29 08:22:09 +00:00
Jonathan Coates
e241575329 Prepare for 1.102.0
This is a stupid tradition.
2022-12-24 10:51:54 +00:00
Jonathan Coates
86c4c7483d Bump Cobalt and ForgeConfigAPI versions
Fixes #1248, fixes #1249
2022-12-21 15:58:51 +00:00
Jonathan Coates
9010219b9c Merge pull request #1262 from emmachase/mc-1.19.x
Fix duplicated swing animations on high-ping servers
2022-12-20 10:26:54 +00:00
emmachase
172d1824fc Fix duplicated swing animations on high-ping servers
Use `InteractionResult.sidedSuccess` / `.CONSUME` where applicable instead of `.SUCCESS`. This prevents the server from sending an additional swing animation packet to the client. Normally this isn't a problem, since the client will de-duplicate swing packets if they are within the animation duration of the currently playing swing; however, when connected to a server with a high ping the packet is sent after the animation is already finished on the client, resulting in a duplicate animation.
2022-12-19 19:53:41 -08:00
Jonathan Coates
9d394f44d3 Merge pull request #1255 from toad-dev/patch/illuaminate-support-arm-macs
Deliver x86_64 Illuaminate binaries to all Macs
2022-12-18 00:00:22 +00:00
David Queneau
6e5b7243f4 Deliver x86_64 Illuaminate binaries to all Macs
The Intel native binaries run just fine on Apple-silicon Macs through
Rosetta.
2022-12-17 14:57:30 -08:00
Jonathan Coates
27b732f835 Make the turtle label move with the turtle
We now perform movement translations before rendering the label, rather
than afterwards. This means the label moves smoothly(ish), rather than
jumping from block to block.
2022-12-15 22:25:14 +00:00
Jonathan Coates
4fa7f50534 Time fs and peripheral operations 2022-12-15 22:12:53 +00:00
Jonathan Coates
eeac86b07c Satiate the demons of checkstyle
I was sure I'd run pre-commit, but clearly not!
2022-12-15 21:42:18 +00:00
Jonathan Coates
36ce490566 Use RenderSystem for setting polygon offsets 2022-12-15 21:24:38 +00:00
Jonathan Coates
e7fe22d4f8 Don't round trip values in executeMainThreadTask
I've been meaning to fix this for over 6 years, and just kept
forgetting.

Previously ILuaContext.executeMainThreadTask worked by running
ILuaContext.issueMainThreadTask, pulling task_complete events, and then
returning the results.

While this makes the implementation simple, it means that the task's
results were converted into Lua values (in order to queue the event) and
then back into Java ones (when the event was pulled), before eventually
being converted into Lua once more.

Not only is this inefficient, as roundtripping isn't lossless, you
couldn't return functions or rich objects from main thread functions
(see https://github.com/dan200/ComputerCraft/issues/125).

We now store the return value on the Java side and then return that when
the receiving the task_complete event - the event no longer carries the
result. Note this does not affect methods using issueMainThreadTask!
2022-12-15 20:19:28 +00:00
Jonathan Coates
2b237332ce Update to latest Forge
This fixes the issue with DeferredRegister crashing on non-wrapped
registries.
2022-12-15 17:53:50 +00:00
Jonathan Coates
1276478deb Use the correct import path in import.lua
Think this is worth backporting to 1.16.5. Ughr.
2022-12-14 21:29:38 +00:00
Jonathan Coates
551f6ba60c Fix out-of-bounds read in ByteBufferChannel
Introduced in fa122a56cf by the looks of
it, so shouldn't have ever made it into a release.
2022-12-14 21:21:47 +00:00
Jonathan Coates
99a2b26fc5 Fix some typos in ARCHITECTURE.md
Mostly just relics of the old multi-loader branch.
2022-12-14 18:49:06 +00:00
Jonathan Coates
0787e17ebe Use git shortlog for gathering contributors 2022-12-13 20:31:59 +00:00
Jonathan Coates
06163e4f25 Fix a couple of packaging issues
- Fix client classes not being included in Forge.
 - Only remap Nettty's HTTP classes, not all of them. This feels a
   little more error prone - maybe we should jar-in-jar this in the
   future.
 - Use the correct refmaps on Forge.
 - Prevent the Fabric jar pulling in some other mods.

Closes #1247
2022-12-12 20:28:18 +00:00
Jonathan Coates
18fbd96c10 Some further improvemnets to mount error handling
- Correctly handle FileOperationExceptions for the root mount.
 - Remove some checks from MountWrapper: Mount/WritableMount should do
   these already!
 - Normalise file paths, always using a '/'.
2022-12-10 12:54:49 +00:00
Jonathan Coates
367773e173 Some refactoring of mounts
- Separate FileMount into separate FileMount and WritableFileMount
   classes. This separates the (relatively simple) read-only code from
   the (soon to be even more complex) read/write code.

   It also allows you to create read-only mounts which don't bother with
   filesystem accounting, which is nice.

 - Make openForWrite/openForAppend always return a SeekableFileHandle.
   Appendable files still cannot be seeked within, but that check is now
   done on the FS side.

 - Refactor the various mount tests to live in test contract interfaces,
   allowing us to reuse them between mounts.

 - Clean up our error handling a little better. (Most) file-specific code
   has been moved to FileMount, and ArchiveMount-derived classes now
   throw correct path-localised exceptions.
2022-12-09 22:02:31 +00:00
Jonathan Coates
8007a30849 Actually update README with the new maven coordinates
Also bump REI to 1.19.3
2022-12-09 18:05:35 +00:00
Jonathan Coates
df38f3e887 Update README with the new maven coordinates
Currently published under 1.102.0-SNAPSHOT if anybody wants/needs to
poke. I'm going to break Mount/WritableMount, but everything else should
be stable!
2022-12-08 21:52:34 +00:00
Jonathan Coates
c3fe9f00d4 Update to Minecraft 1.19.3
Lots of minor changes, but nothing too nasty - just tedious.

Known bugs/issues:
 - REI and JEI haven't been updated at the time of writing, so our usage
   of their APIs may be incompatible.

 - Crash when opening the config UI in Fabric, as forgeconfigapi-port
   hasn't been updated yet.

Will hold off on doing a release until those mods have updated.
2022-12-08 19:45:02 +00:00
Jonathan Coates
3b42f22a4f A couple of fixes to the HTTP API
- Flip http.websocket and http.websocketAsync docs (fixes #1244)

 - Fix http.request queuing a http_failure event with no URL when
   passing a malformed URL

 - Fix http.websocketAsync not queuing websocket_failure events on
   immediate failure.
2022-12-07 21:14:33 +00:00
Jonathan Coates
9962ce1a5c Finish a sentence
Folks have been waiting 2 years for this, it's time to end the suspense.
2022-12-07 09:53:16 +00:00
Jonathan Coates
9f48395596 Correctly format 12AM/PM with %I
Fixes #1243
2022-12-06 21:50:28 +00:00
Jonathan Coates
020c5cd2d3 Support renaming files directly without copying/deleting
In classic squid tradition: 20% code, and 80% test logic.

Closes #962. Alas, whoever reported this has deleted their account, so
they can't even be happy about it :(.
2022-12-04 21:59:30 +00:00
Jonathan Coates
a9c0b02e3c Run core tests on Windows/OSX too
We've a couple of Windows-specific tests here which I've not actually
run on Windows for years. It feels worth doing properly.
2022-12-04 18:41:19 +00:00
Jonathan Coates
fc5f296eeb Make Mount.openForRead always return a SeekableByteChannel
I want to make some further changes to Mount here, but this helps
simplify BinaryReadableHandle a little.
2022-12-03 18:20:50 +00:00
Jonathan Coates
c96172e78d Refactor common {Jar,Resource}Mount code into a parent class 2022-12-03 18:02:12 +00:00
Jonathan Coates
fa122a56cf Resolve a few TODOs
- Update ForgeConfigAPI to the latest version, to fix the race
   condition.
 - Move WirelessNetwork lifecycle management to ServerContext.
 - Some doc fixes.
2022-12-03 15:55:48 +00:00
Jonathan Coates
87c6d3aef6 Initial pass of the API breaking changes for 1.19.3 (#1232)
- Remove deprecated API members in prep for 1.19.3. This allows us to
   remove the mc-stubs and forge-stubs projects.

 - Make several methods take a MinecraftServer instead of a Level (or
   nothing at all).

 - Remove I prefixes from a whole bunch of interfaces, making things a
   little more consistent with Java conventions.

   This avoids touching the "main" interfaces people consume for now. I
   want to do that another Minecraft version, to avoid making the update
   too painful.

 - Remove IFileSystem and associated getters. This has never worked very
   well and I don't think has got much (any?) usage.
2022-12-03 15:02:00 +00:00
Jonathan Coates
95c57e843d Pass project root to cct-javadoc
Fixes #1238
2022-12-02 22:00:00 +00:00
Jonathan Coates
b13998dd96 Allow client tests to fail
I give this commit a 50% chance of failing for other GH Actions related
reasons. Nobody ever gets CI commits right on the first try.
2022-11-25 20:45:43 +00:00
Drew Edwards
47816805fb Check the filesystem for isReadOnly (#1226) 2022-11-25 20:40:40 +00:00
Jonathan Coates
b8fce1eecc Trim spaces from filesystem paths
I kinda hate this, but not sure what else to do. It might be worth
rewriting sanitizePath in the future to loop through the string once,
but I think this is good enough for now.
2022-11-25 20:12:10 +00:00
Jonathan Coates
ee2670d53b Move some functions out of bios into their own APIs
This removes the patching of fs and http, and replaces them with their
own standard Lua APIs. This makes the bios a little simpler, and means
we can move the documentation in line.
2022-11-21 22:47:07 +00:00
Jonathan Coates
3a96aea894 Add a couple of tests for pocket computers
- Ensure they're correctly synced to the client. This definitely isn't
   comprehensive, but doing anything further probably involves multiple
   players, which is tricky.

 - Quick rendering test for in-hand computers.
2022-11-21 21:34:35 +00:00
Jonathan Coates
0fc78acd49 Fix computer upgrade recipes 2022-11-21 19:08:23 +00:00
Jonathan Coates
737d8a2585 Add a couple of tests for inventory transfer
For now this is just a couple of regression tests for a couple of CC:R
bugs.
2022-11-20 19:41:36 +00:00
Jonathan Coates
e2447bb0fd Fix path to Fabric mod icon
Forge requires the file to be in the root of the jar, hence doing it
this way round. The icon is read using ModContainer.findPath, so this
shouldn't conflict with other mods.
2022-11-20 17:31:14 +00:00
Jonathan Coates
2255d49d16 Update to latest Fabric
Love being on the bleedin' edge. More importantly, this fixes a couple
of issues:
 - Translations are loaded on the server, meaning .getItemDetail
   correctly translates modded items.
 - Shulker boxes cannot be moved inside other shulker boxes using the
   transfer API.
 - Start using Fab API's ItemStack.getRecipeRemainder().
2022-11-20 16:05:28 +00:00
Weblate
3fa39b5f98 Translations for Russian (ru_ru)
Translations for Swedish

Translations for French

Co-authored-by: Naheulf <newheulf@gmail.com>
Co-authored-by: SquidDev <git@squiddev.cc>
Co-authored-by: Алексей <handbrake.mine@gmail.com>
2022-11-20 13:16:06 +00:00
Jonathan Coates
08df68dcc0 Generate en_us.json via datagen
I was originally pretty sceptical about this, but it actually ends up
being useful for the same reason any other form of datagen is: we can
ensure that names are well formed, and that every string is actually
translated.

There's some future work here to go through all the custom translation
keys and move them into constants (maybe also do something with the
/computercraft command?), but that's a separate chunk of work.

The main motivation for this is to add translation keys to our config:
the Fabric version of Forge Config API provides a config UI, so it's
useful to provide user-friendly strings. Our generator also
automatically copies comments over, turning them into tooltips.

This also updates all of the other language files to match en_us.json
again: it's a very noisy diff as the file is now sorted alphabetically.
Hopefully this won't affect weblate though

[^1]: Amusing really that the Fabric port actually is more useful than
the original.
2022-11-20 13:00:43 +00:00
Jonathan Coates
8f92417a2f Add a system for client-side tests (#1219)
- Add a new ClientJavaExec Gradle task, which is used for client-side
   tests. This:

   - Copies the exec spec from another JavaExec task.
   - Sets some additional system properties to configure on gametest framework.
   - Runs Java inside an X framebuffer (when available), meaning we
     don't need to spin up a new window.

   We also configure this task so that only one instance can run at
   once, meaning we don't spawn multiple MC windows at once!

 - Port our 1.16 client test framework to 1.19. This is mostly the same
   as before, but screenshots no longer do a golden test: they /just/
   write to a folder. Screenshots are compared manually afterwards.

   This is still pretty brittle, and there's a lot of sleeps scattered
   around in the code. It's not clear how well this will play on CI.

 - Roll our own game test loader, rather than relying on the mod loader
   to do it for us. This ensures that loading is consistent between
   platforms (we already had to do some hacks for Forge) and makes it
   easier to provide custom logic for loading client-only tests.

 - Run several client tests (namely those involving monitor rendering)
   against Sodium and Iris too. There's some nastiness here to set up
   new Loom run configurations and automatically configure Iris to use
   Complementary Shaders, but it's not too bad. These tests /don't/ run
   on CI, so it doesn't need to be as reliable.
2022-11-18 23:57:25 +00:00
Jonathan Coates
b58b9b7df3 Fix several issues with the Fabric API jar
- Bundle the core API inside the Fabric API jar for now, to ensure
   that ResourceLocation is remapped.

 - Add a dummy fabric.mod.json file to the API. We'll remove this once
   https://github.com/FabricMC/fabric-loom/pull/749 is released.
2022-11-18 23:21:05 +00:00
Jonathan Coates
8d2e150f05 Various improvements to packaging
This fixes several issues I had with consuming multi-loader CC:T in
various upstream mods.

 - Include /all/ sources in the Forge/Fabric jar. Before it was just the
   common classes, and not the core or API.

 - Use some Gradle magic to remove superfluous dependencies from the POM
   file. Also make sure Cobalt and Netty are present as dependencies.

 - Start using minimize() in our shadow jar config again.
2022-11-17 09:26:57 +00:00
Jonathan Coates
8152f19b6e Fabric lol
- Add support for Fabric. This is mostly pretty simple, though does
   require a lot more mixins than Forge.

   Half this diff is due to data generators: we run them separately as
   some aspects (recipes mostly) are different between the loaders.

 - Add integration with Iris (same as our Oculus support) and REI
   (mostly the same as our JEI support).

 - Generic peripherals only support inventories (or rather
   InventoryStorage) right now. Supporting more of the Fabric storage
   API is going to be tricky due to the slotted nature of the API: maybe
   something to revisit after Transfer API V3 (V4?, I've lost track).

Note, this does /not/ mean I will be publishing a Fabric version of
CC:T. My plan is to rebase CC:R on top of this, hopefully simplifying
the maintenance work on their end and making the two mods a little more
consistent.
2022-11-10 19:42:34 +00:00
Jonathan Coates
b2b58892e3 Minor fixes and cleanup
My working tree is a mess, so this is not a good commit. I'm making a
bit of a habit of this.

 - Fix UserLevel.OWNER check failing on single player servers.
 - Correctly handle the "open folder" fake command.
 - Some reshuffling of Forge-specific methods to make Fabric slightly
   easier.
2022-11-10 17:15:12 +00:00
Jonathan Coates
8360e8234d Post multi-loader cleanup
This commit got away from me, okay? No, I'm not proud of it either.

 - Remove our overrides of handleUpdate tag: we now try to detect
   whether we're on the client or server inside BlockEntity.load. Alas,
   this is needed for Fabric.

 - Remove BlockGeneric/TileGeneric entirely: we've slowly whittled this
   down over the years, and nowadays we can get away with putting most
   of its functionality into subclasses.

   This allows us to do some nice things with overriding HorizontalBlock
   (or our new HorizontalContainerBlock class), rather than
   reimplementing functionality in each class. Though it would be nice
   if Java had some sort of trait system :D:

 - Simplify a lot of our container class so it's just defined in terms
   of a NonNullList<ItemStack>. This also includes a total rewrite of
   the disk drive which I'm not ... thrilled about. It ended up being
   easier to copy the code from the mc-next branch :D:.

 - Try to test some of the gnarly bits of this. Still a /lot/ more to be
   done with testing this.

Closes #658
2022-11-10 15:55:34 +00:00
Jonathan Coates
77624fc6fd Update project paths in our utility build scripts 2022-11-10 09:12:28 +00:00
Jonathan Coates
1d335f7290 Add a couple of errorprone plugins
- Check that common code does not reference client-only classes.
 - Check that @ForgeOverride really overrides a method in Forge
   projects.
2022-11-10 08:54:09 +00:00
Jonathan Coates
f04acdc199 Split CC:T into common and forge projects
After several weeks of carefully arranging ribbons, we pull the string
and end up with, ... a bit of a messy bow. There were still some things
I'd missed.

 - Split the mod into a common (vanilla-only) project and Forge-specific
   project. This gives us room to add Fabric support later on.

 - Split the project into main/client source sets. This is not currently
   statically checked: we'll do that soon.

 - Rename block/item/tile entities to use suffixes rather than prefixes.
2022-11-10 08:54:09 +00:00
Jonathan Coates
bdf590fa30 Clean up data generators a little 2022-11-09 22:02:47 +00:00
Jonathan Coates
0c4fd2b29e Make the shader mod system a little more flexible
Mostly for multi-loader support, but also /technically/ makes it easier
to support multiple loaders in the future.
2022-11-09 21:05:27 +00:00
Jonathan Coates
8a7156785d Refactor turtle actions for easier multi-loader support
- TurtlePlayer now returns a fake player, rather than extending from
   Forge's implementation.

 - Remove "turtle obeys block protection" config option.

 - Put some of the more complex turtle actions behind the
   PlatformHelper.
2022-11-09 20:50:43 +00:00
Jonathan Coates
4d50b48ea6 Remove all references to the ComputerCraft class
Another ugly commit, not thrilled about it:
 - Move all config values to a separate Config class.
 - Replace ComputerCraft.log with class-specific loggers.
 - Replace ComputerCraft.MOD_ID with ComputerCraftAPI.MOD_ID
2022-11-09 20:10:24 +00:00
Jonathan Coates
48285404b9 Move the rendering monitor code to the client side
It's kinda funny looking at all the pre-CC:T proxy code, how much this
has come full circle. I think this design is nicer than what 1.12 did,
but it's still slighly embarassing.
2022-11-09 19:55:42 +00:00
Jonathan Coates
b36b96e0bc Make the main mod non-null by default
This was actually much more work than I thought it would be. Tests pass,
but I'm sure there's some regressions in here.
2022-11-09 18:59:51 +00:00
Jonathan Coates
34c7fcf750 Refactor the turtle model code a little
Should make it easier to use in a multi-loader setting. This probably
still needs a bit more work: Dinnerbone turtles are still very broken.
2022-11-09 17:21:29 +00:00
Jonathan Coates
cc73fcd85d Move many Forge-specific methods behind PlatformHelper
There's still a lot of work to be done on turtles (TurtlePlayer, all the
turtle tool stuff), but we're a good way along now.
2022-11-09 14:41:46 +00:00
Jonathan Coates
22729f6f16 Abstract our our use of Forge's tags 2022-11-09 10:59:05 +00:00
Jonathan Coates
55494b7671 Rewrite how we do inventory transfers
- Add a new ContainerTransfer class to handle moving items between
   containers. This is now used for turtle.drop/turtle.suck as well as
   inventory methods.

 - Any other usages of IItemHandler (which are mostly on turtle
   inventories) now use Container and a couple of helper methods.
2022-11-08 21:31:24 +00:00
Jonathan Coates
7d47b219c5 Access capabilities using PlatformHelper
We provide a new abstraction: ComponentAccess, which is a view of
capabilities (or block lookups for Fabric) for adjacent blocks.
2022-11-08 17:44:06 +00:00
Jonathan Coates
320007dbc6 Improve packaging of published jars
- Publish javadoc again: for now this is just the common-api

 - Remove all dependencies from the published Forge jar. This is
   technically not needed (fg.deobf does this anyway), but seems
   sensible.
2022-11-08 16:43:27 +00:00
Jonathan Coates
0908acbe9b Move all event handlers to a common class
While this does now involve a little more indirection, by having a
single location for all hooks it's much easier to keep event listeners
consistent between loaders.

The diff is pretty noisy (I've inlined some classes, and ClientRegistry
got a big restructure), but functionality should be the same.
2022-11-08 10:48:22 +00:00
Jonathan Coates
e8f9cdd221 Attach capabilities to our BlockEntities externally
Previously we overrode getCapability on our BlockEntity implementations.
While this is the Proper way to do things, it's obviously impossible to
do in a multi-loader environment.

We now subscribe to the AttachCapabilitiesEvent and add our caps that
way. This does require[^1] some nasty invalidation of caps in a couple
of places, which I'm not wild about.

[^1]: I'm not actually sure it does: we invalidate peripherals and wired
elements when neighbours change, so the explicit invalidation probably
isn't useful.
2022-11-06 22:26:07 +00:00
Jonathan Coates
53abe5e56e Make net code more multi-loader friendly
NetworkMessage follows vanilla's Packet much more closely now: the
handler takes a context argument, which varies between the client and
server.

 - The server handler just returns the ServerPlayer who sent the
   message.
 - The client context provides handleXyz(...) methods for each
   clientbound packet. Our packet subclasses call the appropriate
   method - the implementation of which contains the actual
   implementation.

Doing this allows us to move a whole bunch of client-specific code into
the main client package, dropping several @OnlyIn annotations and
generally reducing the risk of accidentally loading client classes on
the server.

We also abstract out some packet sending and general networking code to
make it easier to use in a multi-loader context.
2022-11-06 20:13:04 +00:00
Jonathan Coates
564752c8dd Flesh out our registry abstraction layer
- Replace our usages of DeferredRegistry with a custom
   RegistrationHelper. This effectively behaves the same, just without
   any references to Forge code!

 - Replace any references to ForgeRegistries with a home-grown
   Registries class. This just fetches the underlying registry and
   proxies the method calls.

 - Change recipe serialisers and loot item conditions to be registered
   the same way as other entries.

 - Move any Forge specific registration code off to the main
   ComputerCraft class.
2022-11-06 18:37:00 +00:00
Jonathan Coates
6d665ad841 Rename Registry to ModRegistry
Avoids the conflict with Minecraft's built-in registry class
2022-11-06 18:37:00 +00:00
Jonathan Coates
9cd728fea9 Update parse-reports to handle new paths 2022-11-06 18:37:00 +00:00
Oliver Caha
1c890e5a5c Fix typo in speaker documentation (#1209) 2022-11-06 18:33:46 +00:00
Jonathan Coates
955b9c7d28 Default Forge/Common API to non-null 2022-11-06 15:50:24 +00:00
Jonathan Coates
76710eec9d Move our public API into separate modules
This adds two new modules: common-api and forge-api, which contain the
common and Forge-specific interfaces for CC's Minecraft-specific API.

We add a new PlatformHelper interface, which abstracts over some of the
loader-specific functionality, such as reading registries[^1] or calling
Forge-specific methods. This interface is then implemented in the main
mod, and loaded via ServiceLoaders.

Some other notes on this:

 - We now split shared and client-specific source code into separate
   modules. This is to make it harder to reference client code on the
   server, thus crashing the game.

   Eventually we'll split the main mod up too into separate source sets
   - this is, of course, a much bigger problem!

 - There's currently some nastiness here due to wanting to preserve
   binary compatibility of the API. We'll hopefully be able to remove
   this when 1.19.3 releases.

 - In order to build a separate Forge-specific API jar, we compile the
   common sources twice: once for the common jar and once for the Forge
   jar.

   Getting this to play nicely with IDEs is a little tricky and so we
   provide a cct.inlineProject(...) helper to handle everything.

[^1]: We /can/ do this with vanilla's APIs, but it gives a lot of
deprecation warnings. It just ends up being nicer to abstract over it.
2022-11-06 15:07:13 +00:00
Jonathan Coates
d8e2161f15 Move website source/build logic to projects/web
Mostly useful as it moves some of our build logic out of the main
project, as that's already pretty noisy!
2022-11-06 13:37:07 +00:00
Jonathan Coates
c82f37d3bf Switch the core library to be non-null by default
See comments in c8c128d335 for further
details. This requires /relatively/ few changes - mostly cases we were
missing @Nullable annotations.
2022-11-06 11:55:26 +00:00
Jonathan Coates
c8c128d335 Switch the core-api to be non-null by default
We'll do this everywhere eventually, but much easier to do it
incrementally:

 - Use checker framework to default all field/methods/parameters to
   @Nonnull.

 - Start using ErrorProne[1] and NullAway[2] to check for possible null
   pointer issues. I did look into using CheckerFramework, but it's much
   stricter (i.e. it's actually Correct). This is technically good, but
   is a much steeper migration path, which I'm not sure we're prepared
   for yet!

[1]: https://github.com/google/error-prone
[2]: https://github.com/uber/NullAway
2022-11-06 10:28:49 +00:00
Jonathan Coates
acc254a1ef Move dan200.computercraft.core into a separate module
This is a very big diff in changed files, but very small in actual
changes.
2022-11-06 10:02:14 +00:00
Jonathan Coates
a17b001950 Move the core API into a separate module
It should be possible to consume the ComputerCraft's core (i.e.
non-Minecraft code) in other projects, such as emulators.  While this
has been possible for years, it's somewhat tricky from a maintenance
perspective - it's very easy to accidentally add an MC dependency
somewhere!

By publishing a separate "core" jar, we can better distinguish the
boundaries between our Lua runtime and the Minecraft-specific code.

Ideally we could have one core project (rather than separate core and
core-api modules), and publish a separate "api" jar, like we do for the
main mod. However, this isn't really possible to express using Maven
dependencies, and so we must resort to this system.

Of course, this is kinda what the Java module system is meant to solve,
but unfortunately getting that working with Minecraft is infeasible.
2022-11-04 21:41:59 +00:00
Jonathan Coates
e4e528e5bf Prepare dan200.computercraft.core for splitting off
- Move core-specific config options to a separate CoreConfig class.
 - Use class-specific loggers, instead of a global one.
 - Use log markers instead of a logComputerErrors option.
2022-11-04 19:58:57 +00:00
Jonathan Coates
6cc86b0ae5 Exclude the previous commit from git blame
This can be added by running the following command:

    git config --global blame.ignoreRevsFile .git-blame-ignore-revs
2022-11-04 13:42:00 +00:00
Jonathan Coates
f478c4ffc4 Reformat everything
- Switch to a fairly standard code format. This is largely based on
   IntelliJ defaults, with some minor tweaks applied via editor config.
   Should mean people don't need to import a config!

 - Use "var" everywhere instead of explicit types. Type inference is a
   joy, and I intend to use it to its fullest.

 - Start using switch expressions: we couldn't use them before because
   IntelliJ does silly things with our previous brace style, but now we
   have the luxury of them!
2022-11-04 13:41:38 +00:00
Jonathan Coates
7df0412c2d Bump CC:T to 1.101.1
Our last palendrome for a while!
2022-11-03 20:41:47 +00:00
Jonathan Coates
998efcc950 Move our test thread scheduler into the main jar
While useless in-game, both CCEmuX and eval.tweaked.cc ended up
reimplementing it, so might as well expose it!
2022-11-03 20:41:47 +00:00
Jonathan Coates
45c5de73bb Correctly register turtle refuel handlers
And actually test them! Along with a whole load of other turtle tests.

Fixes #1202
2022-11-03 20:41:46 +00:00
Alessandro Proto
c919011a7e Check for valid nSender field on RedNet message receive (#1200) 2022-11-03 08:40:37 +00:00
Jonathan Coates
0f1f5247ca Merge branch 'mc-1.18.x' into mc-1.19.x 2022-11-02 09:55:10 +00:00
Jonathan Coates
0db32bd0fe Merge branch 'mc-1.16.x' into mc-1.18.x 2022-11-02 09:55:10 +00:00
Jonathan Coates
1d3ecb551d Improvee changelog 2022-11-02 07:58:31 +00:00
Jonathan Coates
aefda6a381 Fix a couple of issues with the release process
Only way to test these is in prod, alas :D:
2022-11-01 21:53:05 +00:00
2651 changed files with 70967 additions and 58694 deletions

View File

@@ -8,6 +8,17 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
ij_continuation_indent_size = 4
ij_any_do_while_brace_force = if_multiline
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

2
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,2 @@
# Reformat everything
f478c4ffc4fb9fc2200ec9b0bc751d047057ce81

4
.gitattributes vendored
View File

@@ -1,6 +1,6 @@
# Ignore changes in generated files
src/generated/** linguist-generated
src/testMod/server-files/structures linguist-generated
projects/*/src/generated/** linguist-generated
projects/common/src/testMod/resources/data/cctest/structures/* linguist-generated
* text=auto

View File

@@ -33,14 +33,25 @@ jobs:
./gradlew downloadAssets || ./gradlew downloadAssets
./gradlew build
- 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: Prepare Jars
run: |
# Find the main jar and append the git hash onto it.
mkdir -p jars
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
- name: Upload Jar
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: CC-Tweaked
path: build/libs
path: ./jars
- name: Upload coverage
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v3
- name: Parse test reports
run: ./tools/parse-reports.py
@@ -48,3 +59,40 @@ jobs:
- name: Run linters
uses: pre-commit/action@v3.0.0
build-core:
strategy:
fail-fast: false
matrix:
include:
- name: Windows
uses: windows-latest
- name: macOS
uses: macos-latest
name: Test on ${{ matrix.name }}
runs-on: ${{ matrix.uses }}
steps:
- name: Clone repository
uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
- name: Run tests
run: |
./gradlew --configure-on-demand :core:test
- name: Parse test reports
run: python3 ./tools/parse-reports.py
if: ${{ failure() }}

View File

@@ -12,8 +12,8 @@ chmod 600 "$HOME/.ssh/key"
# And upload
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
"$GITHUB_WORKSPACE/build/docs/site/" \
"$GITHUB_WORKSPACE/projects/web/build/site/" \
"$SSH_USER@$SSH_HOST:/$DEST"
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
"$GITHUB_WORKSPACE/build/docs/javadoc/" \
"$GITHUB_WORKSPACE/projects/common-api/build/docs/javadoc/" \
"$SSH_USER@$SSH_HOST:/$DEST/javadoc"

View File

@@ -3,7 +3,7 @@ name: Build documentation
on:
push:
branches:
- mc-1.16.x
- mc-1.19.x
jobs:
make_doc:
@@ -29,7 +29,7 @@ jobs:
run: ./gradlew compileJava --no-daemon || ./gradlew compileJava --no-daemon
- name: Generate documentation
run: ./gradlew docWebsite javadoc --no-daemon
run: ./gradlew docWebsite :common-api:javadoc --no-daemon
- name: Upload documentation
run: .github/workflows/make-doc.sh 2> /dev/null

10
.gitignore vendored
View File

@@ -2,15 +2,17 @@
/classes
/logs
/build
/projects/*/logs
/projects/*/build
/buildSrc/build
/out
/doc/out/
/node_modules
/.jqwik-database
.jqwik-database
# Runtime directories
/run
/run-*
/projects/*/run
*.ipr
*.iws
@@ -23,8 +25,6 @@
/.project
/.settings
/.vscode
bin/
*.launch
/src/generated/resources/.cache
/src/web/mount/*.d.ts
/projects/*/src/generated/resources/.cache

View File

@@ -48,9 +48,7 @@ repos:
exclude: |
(?x)^(
src/generated|
src/test/resources/test-rom/data/json-parsing/|
src/testMod/server-files/|
config/idea/|
projects/[a-z]+/src/generated|
projects/core/src/test/resources/test-rom/data/json-parsing/|
.*\.dfpwm
)

View File

@@ -4,96 +4,96 @@ provides an introduction as to how to get started in helping out.
If you've any other questions, [just ask the community][community] or [open an issue][new-issue].
## 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)
## Reporting issues
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.
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 [Weblate], an online interface for managing language strings. This is synced
automatically with GitHub, so please don't submit PRs adding/changing translations!
## Developing
In order to develop CC: Tweaked, you'll need to download the source code and then run it. This is a pretty simple
process. When building on Windows, Use `gradlew.bat` instead of `./gradlew`.
## Setting up a development environment
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
- **Clone the repository:** `git clone https://github.com/cc-tweaked/CC-Tweaked.git && cd CC-Tweaked`
- **Setup Forge:** `./gradlew build`
- **Run Minecraft:** `./gradlew runClient` (or run the `GradleStart` class from your IDE).
- **Optionally:** For small PRs (especially those only touching Lua code), it may be easier to use GitPod, which
provides a pre-configured environment: [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-2b2b2b?logo=gitpod)](https://gitpod.io/#https://github.com/cc-tweaked/CC-Tweaked/)
- Make sure you've got the following software instealled:
- Java Development Kit (JDK) installed. This can be downloaded from [Adoptium].
- [Git](https://git-scm.com/).
- If you want to work on documentation, [NodeJS][node].
Do note you will need to download the mod after compiling to test.
- Download CC: Tweaked's source code:
```
git clone https://github.com/cc-tweaked/CC-Tweaked.git
cd CC-Tweaked
```
If you want to run CC:T in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from `build/libs`.
These commands may take a few minutes to run the first time, as the environment is set up, but should be much faster
afterwards.
- Build CC: Tweaked with `./gradlew build`. This will be very slow the first time it runs, as it needs to download a
lot of dependencies (and decompile Minecraft several times). Subsequent runs should be much faster!
The following sections describe the more niche sections of CC: Tweaked's build system. Some bits of these are
quite-complex, and (dare I say) over-engineered, so you may wish to ignore them. Well tested/documented PRs are always
preferred (and I'd definitely recommend setting up the tooling if you're doing serious development work), but for
small changes it can be a lot.
- You're now ready to start developing CC: Tweaked. Running `./gradlew :forge:runClient` or
`./gradle :fabric:runClient` will start Minecraft under Forge and Fabric respectively.
### Code linters
CC: Tweaked uses a couple of "linters" on its source code, to enforce a consistent style across the project. While these
are run whenever you submit a PR, it's often useful to run this before committing.
If you want to run CC:T in a normal Minecraft instance, run `./gradlew assemble` and copy the `.jar` from
`projects/forge/build/libs` (for Forge) or `projects/fabric/build/libs` (for Fabric).
- **[Checkstyle]:** Checks Java code to ensure it is consistently formatted. This can be run with `./gradlew build` or
`./gradle check`.
- **[illuaminate]:** Checks Lua code for semantic and styleistic issues. This can be run with `./gradlew lintLua`.
## 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]!
### Documentation
### Testing
When making larger changes, it's may be useful to write a test to make sure your code works as expected.
CC: Tweaked has several test suites, each designed to test something different:
- In order to test CraftOS and its builtin APIs, we have a test suite written in Lua located at
`projects/core/src/test/resources/test-rom/`. These don't rely on any Minecraft code, which means they can run on
emulators, acting as a sort of compliance test.
These tests are written using a test system called "mcfly", heavily inspired by [busted]. Groups of tests go inside
`describe` blocks, and a single test goes inside `it`. Assertions are generally written using `expect` (inspired by
Hamcrest and the like). For instance, `expect(foo):eq("bar")` asserts that your variable `foo` is equal to the
expected value `"bar"`.
These tests can be run with `./gradlew :core:test`.
- In-game functionality, such as the behaviour of blocks and items, is tested using [Minecraft's gametest
system][mc-test] (`projects/common/src/testMod`). These tests spin up a server, spawn a structure for each test, and
then run some code on the blocks defined in that structure.
These tests can be run with `./gradlew runGametest` (or `./gradle :forge:runGametest`/`./gradlew :fabric:runGametest`
for a single loader).
For more information, [see the architecture document][architecture].
## Writing documentation
When writing documentation for [CC: Tweaked's documentation website][docs], it may be useful to build the documentation
and preview it yourself before submitting a PR.
Our documentation generation pipeline is rather complex, and involves invoking several external tools. Most of this
complexity is hidden by Gradle, but you will need to perform some initial setup:
You'll first need to [set up a development environment as above](#setting-up-a-development-environment).
- Install [Node/npm][node].
- Run `npm ci` to install our Node dependencies.
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.
You can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code, writing the resulting
HTML into `./build/docs/site`.
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
markdown features - if you can, do check what the documentation looks like locally!
#### Writing documentation
illuaminate's documentation system is not currently documented (somewhat ironic), but is _largely_ the same as
[ldoc][ldoc]. Documentation comments are written in Markdown,
Our markdown engine does _not_ support GitHub flavoured markdown, and so does not support all the features one might
expect. It is recommended that you build and preview the docs locally first.
When iterating on documentation, you can get Gradle to rebuild the website every time a file changes by running
`./gradlew docWebsite -t`. This will take a couple of seconds to run, but definitely beats running it manually!
### Testing
Thankfully running tests is much simpler than running the documentation generator! `./gradlew check` will run the
entire test suite (and some additional bits of verification).
Before we get into writing tests, it's worth mentioning the various test suites that CC: Tweaked has:
- "Core" Java (`./src/test/java`): These test core bits of the mod which don't require any Minecraft interaction.
This includes the `@LuaFunction` system, file system code, etc...
These tests are run by `./gradlew test`.
- CraftOS (`./src/test/resources/test-rom/`): These tests are written in Lua, and ensure the Lua environment, libraries
and programs work as expected. These are (generally) written to be able to be run on emulators too, to provide some
sort of compliance test.
These tests are run by the '"Core" Java' test suite, and so are also run with `./gradlew test`.
- In-game (`./src/testMod/java/dan200/computercraft/ingame/`): These tests are run on an actual Minecraft server, using
the same system Mojang do][mc-test]. The aim of these is to test in-game behaviour of blocks and peripherals.
These tests are run with `./gradlew runGametest`.
## CraftOS tests
CraftOS's tests are written using a test system called "mcfly", heavily inspired by [busted] (and thus RSpec). Groups of
tests go inside `describe` blocks, and a single test goes inside `it`.
Assertions are generally written using `expect` (inspired by Hamcrest and the like). For instance, `expect(foo):eq("bar")`
asserts that your variable `foo` is equal to the expected value `"bar"`.
When writing long-form documentation (such as the guides in [doc/guides](doc/guides)), I find it useful to tell a
narrative. Think of what you want the user to learn or achieve, then start introducing a simple concept and then talk
about how you can build on that, until you've covered everything!
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
[community]: README.md#Community "Get in touch with the community."
[community]: README.md#community "Get in touch with the community."
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
[checkstyle]: https://checkstyle.org/
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
@@ -102,3 +102,4 @@ asserts that your variable `foo` is equal to the expected value `"bar"`.
[mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg
[busted]: https://github.com/Olivine-Labs/busted "busted: Elegant Lua unit testing."
[node]: https://nodejs.org/en/ "Node.js"
[architecture]: projects/ARCHITECTURE.md

View File

@@ -26,16 +26,26 @@ on is present.
```groovy
repositories {
maven {
url 'https://squiddev.cc/maven/'
url "https://squiddev.cc/maven/"
content {
includeGroup 'org.squiddev'
includeGroup("cc.tweaked")
includeModule("org.squiddev", "Cobalt")
includeModule("fuzs.forgeconfigapiport", "forgeconfigapiport-fabric")
}
}
}
dependencies {
compileOnly fg.deobf("org.squiddev:cc-tweaked-${mc_version}:${cct_version}:api")
runtimeOnly fg.deobf("org.squiddev:cc-tweaked-${mc_version}:${cct_version}")
// Vanilla (i.e. for multi-loader systems)
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api")
// Forge Gradle
compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion"))
runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion"))
// Fabric Loom
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$cctVersion")
modRuntimeOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric:$cctVersion")
}
```

View File

@@ -1,402 +1,18 @@
import cc.tweaked.gradle.*
import net.darkhax.curseforgegradle.TaskPublishCurseForge
import net.minecraftforge.gradle.common.util.RunConfig
import org.jetbrains.gradle.ext.compiler
import org.jetbrains.gradle.ext.settings
plugins {
// Build
alias(libs.plugins.forgeGradle)
alias(libs.plugins.mixinGradle)
alias(libs.plugins.librarian)
alias(libs.plugins.shadow)
// Publishing
`maven-publish`
alias(libs.plugins.curseForgeGradle)
alias(libs.plugins.githubRelease)
alias(libs.plugins.minotaur)
// Utility
publishing
alias(libs.plugins.taskTree)
id("cc-tweaked.illuaminate")
id("cc-tweaked.node")
id("cc-tweaked.gametest")
alias(libs.plugins.githubRelease)
id("org.jetbrains.gradle.plugin.idea-ext")
id("cc-tweaked")
}
val isStable = true
val isUnstable = project.properties["isUnstable"] == "true"
val modVersion: String by extra
val mcVersion: String by extra
group = "org.squiddev"
version = modVersion
base.archivesName.set("cc-tweaked-$mcVersion")
java.registerFeature("extraMods") { usingSourceSet(sourceSets.main.get()) }
sourceSets {
main {
resources.srcDir("src/generated/resources")
}
}
minecraft {
runs {
// configureEach would be better, but we need to eagerly configure configs or otherwise the run task doesn't
// get set up properly.
all {
lazyToken("minecraft_classpath") {
configurations["shade"].copyRecursive().resolve().joinToString(File.pathSeparator) { it.absolutePath }
}
property("forge.logging.markers", "REGISTRIES")
property("forge.logging.console.level", "debug")
forceExit = false
mods.register("computercraft") { source(sourceSets.main.get()) }
}
val client by registering {
workingDirectory(file("run"))
}
val server by registering {
workingDirectory(file("run/server"))
arg("--nogui")
}
val data by registering {
workingDirectory(file("run"))
args(
"--mod",
"computercraft",
"--all",
"--output",
file("src/generated/resources/"),
"--existing",
file("src/main/resources/"),
)
property("cct.pretty-json", "true")
}
fun RunConfig.configureForGameTest() {
val old = lazyTokens.get("minecraft_classpath")
lazyToken("minecraft_classpath") {
// We do some terrible hacks here to basically find all things not already on the runtime classpath
// and add them. /Except/ for our source sets, as those need to load inside the Minecraft classpath.
val testMod = configurations["testModRuntimeClasspath"].resolve()
val implementation = configurations.runtimeClasspath.get().resolve()
val new = (testMod - implementation)
.asSequence()
.filter { it.isFile && !it.name.endsWith("-test-fixtures.jar") }
.map { it.absolutePath }
.joinToString(File.pathSeparator)
if (old == null) new else old.get() + File.pathSeparator + new
}
property("cctest.sources", file("src/testMod/resources/data/cctest").absolutePath)
arg("--mixin.config=computercraft-gametest.mixins.json")
mods.register("cctest") {
source(sourceSets["testMod"])
source(sourceSets["testFixtures"])
}
}
val testClient by registering {
workingDirectory(file("run/testClient"))
parent(client.get())
configureForGameTest()
}
val gameTestServer by registering {
workingDirectory(file("run/testServer"))
configureForGameTest()
property("forge.logging.console.level", "info")
}
}
mappings("parchment", "${libs.versions.parchmentMc.get()}-${libs.versions.parchment.get()}-$mcVersion")
accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
}
mixin {
add(sourceSets.main.get(), "computercraft.mixins.refmap.json")
config("computercraft.mixins.json")
}
reobf {
register("shadowJar")
}
configurations {
val shade by registering { isTransitive = false }
implementation { extendsFrom(shade.get()) }
register("cctJavadoc")
}
dependencies {
minecraft("net.minecraftforge:forge:$mcVersion-${libs.versions.forge.get()}")
annotationProcessor("org.spongepowered:mixin:0.8.5:processor")
compileOnly(libs.jetbrainsAnnotations)
annotationProcessorEverywhere(libs.autoService)
"extraModsCompileOnly"(fg.deobf("mezz.jei:jei-1.19.2-forge-api:11.3.0.262"))
"extraModsCompileOnly"(fg.deobf("mezz.jei:jei-1.19.2-common-api:11.3.0.262"))
"extraModsRuntimeOnly"(fg.deobf("mezz.jei:jei-1.19.2-forge:11.3.0.262"))
"extraModsCompileOnly"(fg.deobf("maven.modrinth:oculus:1.2.5"))
"shade"(libs.cobalt)
"shade"("io.netty:netty-codec-http:4.1.76.Final")
testFixturesApi(libs.bundles.test)
testFixturesApi(libs.bundles.kotlin)
testImplementation(libs.bundles.test)
testImplementation(libs.bundles.kotlin)
testRuntimeOnly(libs.bundles.testRuntime)
"cctJavadoc"(libs.cctJavadoc)
}
illuaminate {
version.set(libs.versions.illuaminate)
}
// Compile tasks
tasks.javadoc {
include("dan200/computercraft/api/**/*.java")
(options as StandardJavadocDocletOptions).links("https://docs.oracle.com/en/java/javase/17/docs/api/")
}
val apiJar by tasks.registering(Jar::class) {
archiveClassifier.set("api")
from(sourceSets.main.get().output) {
include("dan200/computercraft/api/**/*")
}
}
tasks.assemble { dependsOn(apiJar) }
val luaJavadoc by tasks.registering(Javadoc::class) {
description = "Generates documentation for Java-side Lua functions."
group = JavaBasePlugin.DOCUMENTATION_GROUP
source(sourceSets.main.get().java)
setDestinationDir(buildDir.resolve("docs/luaJavadoc"))
classpath = sourceSets.main.get().compileClasspath
options.docletpath = configurations["cctJavadoc"].files.toList()
options.doclet = "cc.tweaked.javadoc.LuaDoclet"
(options as StandardJavadocDocletOptions).noTimestamp(false)
javadocTool.set(
javaToolchains.javadocToolFor {
languageVersion.set(cc.tweaked.gradle.CCTweakedPlugin.JAVA_VERSION)
},
)
}
tasks.processResources {
inputs.property("modVersion", modVersion)
inputs.property("forgeVersion", libs.versions.forge.get())
inputs.property("gitHash", cct.gitHash)
filesMatching("data/computercraft/lua/rom/help/credits.txt") {
expand(mapOf("gitContributors" to cct.gitContributors.get().joinToString("\n")))
}
filesMatching("META-INF/mods.toml") {
expand(mapOf("forgeVersion" to libs.versions.forge.get(), "file" to mapOf("jarVersion" to modVersion)))
}
}
tasks.jar {
isReproducibleFileOrder = true
isPreserveFileTimestamps = false
finalizedBy("reobfJar")
archiveClassifier.set("slim")
manifest {
attributes(
"Specification-Title" to "computercraft",
"Specification-Vendor" to "SquidDev",
"Specification-Version" to "1",
"Implementation-Title" to "cctweaked",
"Implementation-Version" to modVersion,
"Implementation-Vendor" to "SquidDev",
)
}
}
tasks.shadowJar {
finalizedBy("reobfShadowJar")
archiveClassifier.set("")
configurations = listOf(project.configurations["shade"])
relocate("org.squiddev.cobalt", "cc.tweaked.internal.cobalt")
minimize()
}
tasks.assemble { dependsOn("shadowJar") }
// Web tasks
val rollup by tasks.registering(NpxExecToDir::class) {
group = LifecycleBasePlugin.BUILD_GROUP
description = "Bundles JS into rollup"
// Sources
inputs.files(fileTree("src/web")).withPropertyName("sources")
// Config files
inputs.file("tsconfig.json").withPropertyName("Typescript config")
inputs.file("rollup.config.js").withPropertyName("Rollup config")
// Output directory. Also defined in illuaminate.sexp and rollup.config.js
output.set(buildDir.resolve("rollup"))
args = listOf("rollup", "--config", "rollup.config.js")
}
val illuaminateDocs by tasks.registering(IlluaminateExecToDir::class) {
group = JavaBasePlugin.DOCUMENTATION_GROUP
description = "Generates docs using Illuaminate"
// Config files
inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp")
// Sources
inputs.files(fileTree("doc")).withPropertyName("docs")
inputs.files(fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(luaJavadoc)
// Additional assets
inputs.files(rollup)
inputs.file("src/web/styles.css").withPropertyName("styles")
// Output directory. Also defined in illuaminate.sexp and transform.tsx
output.set(buildDir.resolve("illuaminate"))
args = listOf("doc-gen")
}
val jsxDocs by tasks.registering(NpxExecToDir::class) {
group = JavaBasePlugin.DOCUMENTATION_GROUP
description = "Post-processes documentation to statically render some dynamic content."
// Config files
inputs.file("tsconfig.json").withPropertyName("Typescript config")
// Sources
inputs.files(fileTree("src/web")).withPropertyName("sources")
inputs.file("src/generated/export/index.json").withPropertyName("export")
inputs.files(illuaminateDocs)
// Output directory. Also defined in src/web/transform.tsx
output.set(buildDir.resolve("jsxDocs"))
args = listOf("ts-node", "-T", "--esm", "src/web/transform.tsx")
}
val docWebsite by tasks.registering(Copy::class) {
group = JavaBasePlugin.DOCUMENTATION_GROUP
description = "Assemble docs and assets together into the documentation website."
from(jsxDocs)
from("doc") {
include("logo.png")
include("images/**")
}
from(rollup) { exclude("index.js") }
from(illuaminateDocs) { exclude("**/*.html") }
from("src/generated/export/items") { into("images/items") }
into(buildDir.resolve("docs/site"))
}
// Check tasks
tasks.test {
systemProperty("cct.test-files", buildDir.resolve("tmp/testFiles").absolutePath)
}
val lintLua by tasks.registering(IlluaminateExec::class) {
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Lint Lua (and Lua docs) with illuaminate"
// Config files
inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp")
// Sources
inputs.files(fileTree("doc")).withPropertyName("docs")
inputs.files(fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(luaJavadoc)
args = listOf("lint")
doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}
val runGametest by tasks.registering(JavaExec::class) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Runs tests on a temporary Minecraft instance."
dependsOn("cleanRunGametest")
// Copy from runGameTestServer. We do it in this slightly odd way as runGameTestServer
// isn't created until the task is configured (which is no good for us).
val exec = tasks.getByName<JavaExec>("runGameTestServer")
dependsOn(exec.dependsOn)
exec.copyToFull(this)
}
cct.jacoco(runGametest)
tasks.check { dependsOn(runGametest) }
// Upload tasks
val checkChangelog by tasks.registering(CheckChangelog::class) {
version.set(modVersion)
whatsNew.set(file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md"))
changelog.set(file("src/main/resources/data/computercraft/lua/rom/help/changelog.md"))
}
tasks.check { dependsOn(checkChangelog) }
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", tasks.shadowJar)
dependsOn(tasks.shadowJar) // Ughr.
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 (isStable) "release" else "alpha"
mainFile.gameVersions.add(mcVersion)
}
tasks.publish { dependsOn(publishCurseForge) }
modrinth {
token.set(findProperty("modrinthApiKey") as String? ?: "")
projectId.set("gu7yAYhd")
versionNumber.set("$mcVersion-$modVersion")
versionName.set(modVersion)
versionType.set(if (isStable) "release" else "alpha")
uploadFile.set(tasks.shadowJar as Any)
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).")
syncBodyFrom.set(provider { file("doc/mod-page.md").readText() })
}
tasks.publish { dependsOn(tasks.modrinth) }
githubRelease {
token(findProperty("githubApiKey") as String? ?: "")
owner.set("cc-tweaked")
@@ -407,52 +23,30 @@ githubRelease {
releaseName.set("[$mcVersion] $modVersion")
body.set(
provider {
"## " + file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
"## " + 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(!isStable)
prerelease.set(isUnstable)
}
tasks.publish { dependsOn(tasks.githubRelease) }
publishing {
publications {
register<MavenPublication>("maven") {
artifactId = base.archivesName.get()
from(components["java"])
artifact(apiJar)
fg.component(this)
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")
scm {
url.set("https://github.com/cc-tweaked/CC-Tweaked.git")
}
issueManagement {
system.set("github")
url.set("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")
}
}
idea.project.settings.compiler.javac {
// We want ErrorProne to be present when compiling via IntelliJ, as it offers some helpful warnings
// and errors. Loop through our source sets and find the appropriate flags.
moduleJavacAdditionalOptions = subprojects
.asSequence()
.map { evaluationDependsOn(it.path) }
.flatMap { project ->
val sourceSets = project.extensions.findByType(SourceSetContainer::class) ?: return@flatMap sequenceOf()
sourceSets.asSequence().map { sourceSet ->
val name = "${idea.project.name}.${project.name}.${sourceSet.name}"
val compile = project.tasks.named(sourceSet.compileJavaTaskName, JavaCompile::class).get()
name to compile.options.allCompilerArgs.joinToString(" ") { if (it.contains(" ")) "\"$it\"" else it }
}
}
}
repositories {
maven("https://squiddev.cc/maven") {
name = "SquidDev"
}
}
.toMap()
}

View File

@@ -3,14 +3,51 @@ plugins {
`kotlin-dsl`
}
// Duplicated in settings.gradle.kts
repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.minecraftforge.net") {
name = "Forge"
content {
includeGroup("net.minecraftforge")
includeGroup("net.minecraftforge.gradle")
}
}
maven("https://maven.parchmentmc.org") {
name = "Librarian"
content {
includeGroupByRegex("^org\\.parchmentmc.*")
}
}
maven("https://repo.spongepowered.org/repository/maven-public/") {
name = "Sponge"
content {
includeGroup("org.spongepowered")
}
}
maven("https://maven.fabricmc.net/") {
name = "Fabric"
content {
includeGroup("net.fabricmc")
}
}
}
dependencies {
implementation(libs.errorProne.plugin)
implementation(libs.kotlin.plugin)
implementation(libs.spotless)
implementation(libs.fabric.loom)
implementation(libs.forgeGradle)
implementation(libs.librarian)
implementation(libs.quiltflower)
implementation(libs.vanillaGradle)
}
gradlePlugin {

View File

@@ -0,0 +1,66 @@
/** Default configuration for Fabric projects. */
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
`java-library`
id("fabric-loom")
id("io.github.juuxel.loom-quiltflower")
id("cc-tweaked.java-convention")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
repositories {
maven("https://maven.parchmentmc.org/") {
name = "Parchment"
content {
includeGroup("org.parchmentmc.data")
}
}
}
loom {
splitEnvironmentSourceSets()
splitModDependencies.set(true)
}
MinecraftConfigurations.setup(project)
extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = "fabric")
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
minecraft("com.mojang:minecraft:$mcVersion")
mappings(
loom.layered {
officialMojangMappings()
parchment(
project.dependencies.create(
group = "org.parchmentmc.data",
name = "parchment-${libs.findVersion("parchmentMc").get()}",
version = libs.findVersion("parchment").get().toString(),
ext = "zip",
),
)
},
)
modImplementation(libs.findLibrary("fabric-loader").get())
modImplementation(libs.findLibrary("fabric-api").get())
// Depend on error prone annotations to silence a lot of compile warnings.
compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
}
tasks.ideaSyncTask {
doLast { IdeaRunConfigurations(project).patch() }
}

View File

@@ -0,0 +1,39 @@
/** Default configuration for Forge projects. */
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("cc-tweaked.java-convention")
id("net.minecraftforge.gradle")
id("org.parchmentmc.librarian.forgegradle")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
}
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

@@ -1,4 +1,5 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
/**
* Sets up the configurations for writing game tests.
@@ -11,12 +12,13 @@ plugins {
id("cc-tweaked.java-convention")
}
val main = sourceSets.main.get()
val main = sourceSets["main"]
val client = sourceSets["client"]
// Both testMod and testFixtures inherit from the main classpath, just so we have access to Minecraft classes.
// 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
runtimeClasspath += main.runtimeClasspath
compileClasspath += main.compileClasspath + client.compileClasspath
runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
}
configurations {
@@ -32,12 +34,13 @@ configurations {
// 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)
}
// Similar to java-test-fixtures, but tries to avoid putting the obfuscated jar on the classpath.
val testFixtures by sourceSets.creating {
compileClasspath += main.compileClasspath
compileClasspath += main.compileClasspath + client.compileClasspath
}
java.registerFeature("testFixtures") {
@@ -46,8 +49,12 @@ java.registerFeature("testFixtures") {
}
dependencies {
add(testFixtures.implementationConfigurationName, main.output)
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
add(testFixtures.apiConfigurationName, libs.findBundle("test").get())
// Consumers of this project already have the common and client classes on the classpath, so it's fine for these
// to be compile-only.
add(testFixtures.compileOnlyApiConfigurationName, commonClasses(project))
add(testFixtures.compileOnlyApiConfigurationName, clientClasses(project))
testImplementation(testFixtures(project))
add(testMod.implementationConfigurationName, testFixtures(project))
}

View File

@@ -1,23 +1,35 @@
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.LicenseHeader
import com.diffplug.gradle.spotless.FormatExtension
import com.diffplug.spotless.LineEnding
import net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
import java.nio.charset.StandardCharsets
plugins {
`java-library`
idea
jacoco
checkstyle
id("com.diffplug.spotless")
id("net.ltgt.errorprone")
}
val modVersion: String by extra
val mcVersion: String by extra
group = "cc.tweaked"
version = modVersion
base.archivesName.convention("cc-tweaked-$mcVersion-${project.name}")
java {
toolchain {
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
}
withSourcesJar()
withJavadocJar()
}
repositories {
@@ -28,10 +40,15 @@ repositories {
includeGroup("org.squiddev")
includeGroup("cc.tweaked")
// Things we mirror
includeGroup("com.blamejared.crafttweaker")
includeGroup("commoble.morered")
includeGroup("dev.architectury")
includeGroup("maven.modrinth")
includeGroup("me.shedaniel")
includeGroup("me.shedaniel.cloth")
includeGroup("mezz.jei")
includeModule("com.terraformersmc", "modmenu")
includeModule("fuzs.forgeconfigapiport", "forgeconfigapiport-fabric")
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
includeModule("org.spongepowered", "mixin")
}
}
}
@@ -39,6 +56,9 @@ repositories {
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
checkstyle(libs.findLibrary("checkstyle").get())
errorprone(libs.findLibrary("errorProne-core").get())
errorprone(libs.findLibrary("nullAway").get())
}
// Configure default JavaCompile tasks with our arguments.
@@ -46,13 +66,68 @@ sourceSets.all {
tasks.named(compileJavaTaskName, JavaCompile::class.java) {
// Processing just gives us "No processor claimed any of these annotations", so skip that!
options.compilerArgs.addAll(listOf("-Xlint", "-Xlint:-processing"))
options.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("ReferenceEquality", CheckSeverity.OFF)
check("UnusedVariable", CheckSeverity.OFF) // Too many false positives with records.
check("OperatorPrecedence", CheckSeverity.OFF) // For now.
check("AlreadyChecked", CheckSeverity.OFF) // Seems to be broken?
check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid
check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
check("NullAway", CheckSeverity.ERROR)
option("NullAway:AnnotatedPackages", listOf("dan200.computercraft", "net.fabricmc.fabric.api").joinToString(","))
option("NullAway:ExcludedFieldAnnotations", listOf("org.spongepowered.asm.mixin.Shadow").joinToString(","))
option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull")
option("NullAway:CheckOptionalEmptiness")
option("NullAway:AcknowledgeRestrictiveAnnotations")
}
}
}
tasks.compileTestJava {
options.errorprone {
check("NullAway", CheckSeverity.OFF)
}
}
tasks.withType(JavaCompile::class.java).configureEach {
options.encoding = "UTF-8"
}
tasks.withType(AbstractArchiveTask::class.java).configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
dirMode = Integer.valueOf("755", 8)
fileMode = Integer.valueOf("664", 8)
}
tasks.jar {
manifest {
attributes(
"Specification-Title" to "computercraft",
"Specification-Vendor" to "SquidDev",
"Specification-Version" to "1",
"Implementation-Title" to "cctweaked-${project.name}",
"Implementation-Version" to modVersion,
"Implementation-Vendor" to "SquidDev",
)
}
}
tasks.javadoc {
options {
val stdOptions = this as StandardJavadocDocletOptions
stdOptions.addBooleanOption("Xdoclint:all,-missing", true)
stdOptions.links("https://docs.oracle.com/en/java/javase/17/docs/api/")
}
}
tasks.test {
finalizedBy("jacocoTestReport")
@@ -67,6 +142,14 @@ tasks.withType(JacocoReport::class.java).configureEach {
reports.html.required.set(true)
}
project.plugins.withType(CCTweakedPlugin::class.java) {
// Set up jacoco to read from /all/ our source directories.
val cct = project.extensions.getByType<CCTweakedExtension>()
project.tasks.named("jacocoTestReport", JacocoReport::class.java) {
for (ref in cct.sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories)
}
}
spotless {
encoding = StandardCharsets.UTF_8
lineEndings = LineEnding.UNIX
@@ -78,8 +161,8 @@ spotless {
}
val licenser = LicenseHeader.create(
api = file("config/license/api.txt"),
main = file("config/license/main.txt"),
api = rootProject.file("config/license/api.txt"),
main = rootProject.file("config/license/main.txt"),
)
java {
@@ -104,3 +187,12 @@ spotless {
ktlint().editorConfigOverride(ktlintConfig)
}
}
idea.module {
excludeDirs.addAll(project.files("run", "out", "logs").files)
// 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

@@ -0,0 +1,45 @@
import org.gradle.kotlin.dsl.`maven-publish`
plugins {
`java-library`
`maven-publish`
}
publishing {
publications {
register<MavenPublication>("maven") {
artifactId = base.archivesName.get()
from(components["java"])
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")
scm {
url.set("https://github.com/cc-tweaked/CC-Tweaked.git")
}
issueManagement {
system.set("github")
url.set("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")
}
}
}
}
}
repositories {
maven("https://squiddev.cc/maven") {
name = "SquidDev"
credentials(PasswordCredentials::class)
}
}
}

View File

@@ -0,0 +1,31 @@
/** Default configuration for non-modloader-specific Minecraft projects. */
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("cc-tweaked.java-convention")
id("org.spongepowered.gradle.vanilla")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
version(mcVersion)
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
// Depend on error prone annotations to silence a lot of compile warnings.
compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
}
MinecraftConfigurations.setup(project)
extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = null)
}

View File

@@ -1,23 +1,35 @@
package cc.tweaked.gradle
import net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
import org.gradle.api.GradleException
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.attributes.TestSuiteType
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.reporting.ReportingExtension
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.configurationcache.extensions.capitalized
import org.gradle.kotlin.dsl.get
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
import java.io.BufferedWriter
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
import java.io.IOException
import java.io.OutputStreamWriter
import java.net.URI
import java.net.URL
import java.util.regex.Pattern
abstract class CCTweakedExtension(
@@ -26,45 +38,137 @@ abstract class CCTweakedExtension(
) {
/** Get the hash of the latest git commit. */
val gitHash: Provider<String> = gitProvider(project, "<no git hash>") {
ProcessHelpers.captureOut("git", "-C", project.projectDir.absolutePath, "rev-parse", "HEAD")
ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "HEAD").trim()
}
/** Get the current git branch. */
val gitBranch: Provider<String> = gitProvider(project, "<no git branch>") {
ProcessHelpers.captureOut("git", "-C", project.projectDir.absolutePath, "rev-parse", "--abbrev-ref", "HEAD")
ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "--abbrev-ref", "HEAD")
.trim()
}
/** Get a list of all contributors to the project. */
val gitContributors: Provider<List<String>> = gitProvider(project, listOf()) {
val authors: Set<String> = HashSet(
ProcessHelpers.captureLines(
"git", "-C", project.projectDir.absolutePath, "log",
"--format=tformat:%an <%ae>%n%cn <%ce>%n%(trailers:key=Co-authored-by,valueonly)",
),
ProcessHelpers.captureLines(
"git", "-C", project.rootProject.projectDir.absolutePath, "shortlog", "-ns",
"--group=author", "--group=trailer:co-authored-by", "HEAD",
)
val process = ProcessHelpers.startProcess("git", "check-mailmap", "--stdin")
BufferedWriter(OutputStreamWriter(process.outputStream)).use { writer ->
for (authorName in authors) {
var author = authorName
if (author.isEmpty()) continue
if (!author.endsWith(">")) author += ">" // Some commits have broken Co-Authored-By lines!
writer.write(author)
writer.newLine()
.asSequence()
.map {
val matcher = COMMIT_COUNTS.matcher(it)
matcher.find()
matcher.group(1)
}
}
val contributors: MutableSet<String> = HashSet()
for (authorLine in ProcessHelpers.captureLines(process)) {
val matcher = EMAIL.matcher(authorLine)
matcher.find()
val name = matcher.group(1)
if (!IGNORED_USERS.contains(name)) contributors.add(name)
}
contributors.sortedWith(String.CASE_INSENSITIVE_ORDER)
.filter { !IGNORED_USERS.contains(it) }
.toList()
.sortedWith(String.CASE_INSENSITIVE_ORDER)
}
fun jacoco(task: NamedDomainObjectProvider<JavaExec>) {
/**
* References to other sources
*/
val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java)
/** All source sets referenced by this project. */
val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } }
init {
sourceDirectories.finalizeValueOnRead()
project.afterEvaluate { sourceDirectories.disallowChanges() }
}
/**
* Mark this project as consuming another project. Its [sourceDirectories] are added, allowing easier configuration
* of run configurations and other tasks which consume sources/classes.
*/
fun externalSources(project: Project) {
val otherCct = project.extensions.getByType(CCTweakedExtension::class.java)
for (sourceSet in otherCct.sourceDirectories.get()) {
sourceDirectories.add(SourceSetReference(sourceSet.sourceSet, classes = sourceSet.classes, external = true))
}
}
/**
* Add a dependency on another project such that its sources and compiles are processed with this one.
*
* This is used when importing a common library into a loader-specific one, as we want to compile sources using
* the loader-specific sources.
*/
fun inlineProject(path: String) {
val otherProject = project.evaluationDependsOn(path)
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)
// The extra source-processing tasks should include these files too.
project.tasks.named(main.javadocTaskName, Javadoc::class.java) { source(main.allJava, client.allJava) }
project.tasks.named(main.sourcesJarTaskName, Jar::class.java) { from(main.allSource, client.allSource) }
sourceDirectories.addAll(SourceSetReference.inline(main), SourceSetReference.inline(client))
}
/**
* Extend a source set with files from another project.
*
* This actually extends the original compile tasks, as extending the source sets does not play well with IDEs.
*/
private fun extendSourceSet(otherProject: Project, sourceSet: SourceSet) {
project.tasks.named(sourceSet.compileJavaTaskName, JavaCompile::class.java) {
dependsOn(otherProject.tasks.named(sourceSet.compileJavaTaskName)) // Avoid duplicate compile errors
source(sourceSet.allJava)
}
project.tasks.named(sourceSet.processResourcesTaskName, ProcessResources::class.java) {
from(sourceSet.resources)
}
// Also try to depend on Kotlin if it exists
val kotlin = otherProject.extensions.findByType(KotlinProjectExtension::class.java)
if (kotlin != null) {
val compileKotlin = sourceSet.getCompileTaskName("kotlin")
project.tasks.named(compileKotlin, KotlinCompile::class.java) {
dependsOn(otherProject.tasks.named(compileKotlin))
source(kotlin.sourceSets.getByName(sourceSet.name).kotlin)
}
}
// If we're doing an IDE sync, add a fake dependency to ensure it's on the classpath.
if (isIdeSync) project.dependencies.add(sourceSet.apiConfigurationName, sourceSet.output)
}
fun linters(@Suppress("UNUSED_PARAMETER") vararg unused: UseNamedArgs, minecraft: Boolean, loader: String?) {
val java = project.extensions.getByType(JavaPluginExtension::class.java)
val sourceSets = java.sourceSets
project.dependencies.run { add("errorprone", project(mapOf("path" to ":lints"))) }
sourceSets.all {
val name = name
project.tasks.named(compileJavaTaskName, JavaCompile::class.java) {
options.errorprone {
// Only the main source set should run the side checker
check("SideChecker", if (minecraft && name == "main") CheckSeverity.DEFAULT else CheckSeverity.OFF)
// The MissingLoaderOverride check superseeds the MissingOverride one, so disable that.
if (loader != null) {
check("MissingOverride", CheckSeverity.OFF)
option("ModLoader", loader)
} else {
check("LoaderOverride", CheckSeverity.OFF)
check("MissingLoaderOverride", CheckSeverity.OFF)
}
}
}
}
}
fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions {
val classDump = project.buildDir.resolve("jacocoClassDump/${task.name}")
val reportTaskName = "jacoco${task.name.capitalized()}Report"
@@ -93,8 +197,7 @@ abstract class CCTweakedExtension(
classDirectories.from(classDump)
// Don't want to use sourceSets(...) here as we have a custom class directory.
val sourceSets = project.extensions.getByType(SourceSetContainer::class.java)
sourceDirectories.from(sourceSets["main"].allSource.sourceDirectories)
for (ref in sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories)
}
project.extensions.configure(ReportingExtension::class.java) {
@@ -104,8 +207,43 @@ abstract class CCTweakedExtension(
}
}
/**
* Download a file by creating a dummy Ivy repository.
*
* This should only be used for one-off downloads. Using a more conventional Ivy or Maven repository is preferred
* where possible.
*/
fun downloadFile(label: String, url: String): File {
val url = URL(url)
val path = File(url.path)
project.repositories.ivy {
name = label
setUrl(URI(url.protocol, url.userInfo, url.host, url.port, path.parent, null, null))
patternLayout {
artifact("[artifact].[ext]")
}
metadataSources {
artifact()
}
content {
includeModule("cc.tweaked.internal", path.nameWithoutExtension)
}
}
return project.configurations.detachedConfiguration(
project.dependencies.create(
mapOf(
"group" to "cc.tweaked.internal",
"name" to path.nameWithoutExtension,
"ext" to path.extension,
),
),
).resolve().single()
}
companion object {
private val EMAIL = Pattern.compile("^([^<]+) <.+>$")
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
private val IGNORED_USERS = setOf(
"GitHub", "Daniel Ratcliffe", "Weblate",
)
@@ -117,8 +255,14 @@ abstract class CCTweakedExtension(
} 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

@@ -2,6 +2,8 @@ package cc.tweaked.gradle
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.jvm.toolchain.JavaLanguageVersion
/**
@@ -9,7 +11,12 @@ import org.gradle.jvm.toolchain.JavaLanguageVersion
*/
class CCTweakedPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.extensions.create("cct", CCTweakedExtension::class.java)
val cct = project.extensions.create("cct", CCTweakedExtension::class.java)
project.plugins.withType(JavaPlugin::class.java) {
val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main")))
}
}
companion object {

View File

@@ -6,7 +6,6 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.language.base.plugins.LifecycleBasePlugin
import java.nio.charset.StandardCharsets
/**
* Checks the `changelog.md` and `whatsnew.md` files are well-formed.
@@ -35,7 +34,7 @@ abstract class CheckChangelog : DefaultTask() {
var ok = true
// Check we're targetting the current version
// Check we're targeting the current version
var whatsNew = whatsNew.get().asFile.readLines()
if (whatsNew[0] != "New features in CC: Tweaked $version") {
ok = false

View File

@@ -5,7 +5,6 @@ import com.diffplug.spotless.FormatterStep
import com.diffplug.spotless.generic.LicenseHeaderStep
import java.io.File
import java.io.Serializable
import java.nio.charset.StandardCharsets
/**
* Similar to [LicenseHeaderStep], but supports multiple licenses.

View File

@@ -2,19 +2,116 @@ package cc.tweaked.gradle
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.tasks.JavaExec
import org.gradle.process.BaseExecSpec
import org.gradle.process.JavaExecSpec
import org.gradle.process.ProcessForkOptions
/**
* Add an annotation processor to all source sets.
*/
fun DependencyHandler.annotationProcessorEverywhere(dep: Any) {
add("compileOnly", dep)
add("annotationProcessor", dep)
add("clientCompileOnly", dep)
add("clientAnnotationProcessor", dep)
add("testCompileOnly", dep)
add("testAnnotationProcessor", dep)
}
/**
* A version of [JavaExecSpec.copyTo] which copies *all* properties.
*/
fun JavaExec.copyToFull(spec: JavaExec) {
copyTo(spec)
spec.classpath = classpath
spec.mainClass.set(mainClass)
spec.javaLauncher.set(javaLauncher)
// Additional Java options
spec.jvmArgs = jvmArgs // Fabric overrides getJvmArgs so copyTo doesn't do the right thing.
spec.args = args
spec.argumentProviders.addAll(argumentProviders)
spec.mainClass.set(mainClass)
spec.classpath = classpath
spec.javaLauncher.set(javaLauncher)
if (executable != null) spec.setExecutable(executable!!)
// Additional ExecSpec options
copyToExec(spec)
}
/**
* Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo].
*/
fun BaseExecSpec.copyToExec(spec: BaseExecSpec) {
spec.isIgnoreExitValue = isIgnoreExitValue
if (standardInput != null) spec.standardInput = standardInput
if (standardOutput != null) spec.standardOutput = standardOutput
if (errorOutput != null) spec.errorOutput = errorOutput
}
/**
* An alternative to [Nothing] with a more descriptive name. Use to enforce calling a function with named arguments:
*
* ```kotlin
* fun f(vararg unused: UseNamedArgs, arg1: Int, arg2: Int) {
* // ...
* }
* ```
*/
class UseNamedArgs private constructor()
/**
* An [AutoCloseable] implementation which can be used to combine other [AutoCloseable] instances.
*
* Values which implement [AutoCloseable] can be dynamically registered with [CloseScope.add]. When the scope is closed,
* each value is closed in the opposite order.
*
* This is largely intended for cases where it's not appropriate to nest [AutoCloseable.use], for instance when nested
* would be too deep.
*/
class CloseScope : AutoCloseable {
private val toClose = ArrayDeque<AutoCloseable>()
/**
* Add a value to be closed when this scope is closed.
*/
public fun add(value: AutoCloseable) {
toClose.addLast(value)
}
override fun close() {
close(null)
}
@PublishedApi
internal fun close(baseException: Throwable?) {
var exception = baseException
while (true) {
var toClose = toClose.removeLastOrNull() ?: break
try {
toClose.close()
} catch (e: Throwable) {
if (exception == null) {
exception = e
} else {
exception.addSuppressed(e)
}
}
}
if (exception != null) throw exception
}
inline fun <R> use(block: (CloseScope) -> R): R {
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
close(exception)
}
}
}

View File

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

View File

@@ -66,9 +66,11 @@ class IlluaminatePlugin : Plugin<Project> {
val osArch = System.getProperty("os.arch").toLowerCase()
val arch = when {
// On macOS the x86_64 binary will work for both ARM and Intel Macs through Rosetta.
os == "macos" -> "x86_64"
osArch == "arm" || osArch.startsWith("aarch") -> error("Unsupported architecture '$osArch' for illuaminate")
osArch.contains("64") -> "x86_64"
else -> error("Unsupported architecture $osArch for illuaminate")
else -> error("Unsupported architecture '$osArch' for illuaminate")
}
return project.dependencies.create(

View File

@@ -0,0 +1,62 @@
package cc.tweaked.gradle
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.MinimalExternalModuleDependency
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 {
(dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
(dep.name.isNullOrEmpty() || dep.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

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

View File

@@ -0,0 +1,191 @@
package cc.tweaked.gradle
import org.gradle.api.GradleException
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.invocation.Gradle
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.getByName
import org.gradle.language.base.plugins.LifecycleBasePlugin
import java.io.File
import java.nio.file.Files
import java.util.concurrent.TimeUnit
import java.util.function.Supplier
import javax.inject.Inject
import kotlin.random.Random
/**
* A [JavaExec] task for client-tests. This sets some common setup, and uses [MinecraftRunnerService] to ensure only one
* test runs at once.
*/
abstract class ClientJavaExec : JavaExec() {
private val clientRunner: Provider<MinecraftRunnerService> = MinecraftRunnerService.get(project.gradle)
init {
group = LifecycleBasePlugin.VERIFICATION_GROUP
usesService(clientRunner)
}
/**
* When [false], tests will not be run automatically, allowing the user to debug rendering.
*/
@get:Input
val clientDebug get() = project.hasProperty("clientDebug")
/**
* When [false], tests will not run under a framebuffer.
*/
@get:Input
val useFramebuffer get() = !clientDebug && !project.hasProperty("clientNoFramebuffer")
/**
* The path test results are written to.
*/
@get:OutputFile
val testResults = project.layout.buildDirectory.file("test-results/$name.xml")
/**
* Copy configuration from a task with the given name.
*/
fun copyFrom(path: String) = copyFrom(project.tasks.getByName(path, JavaExec::class))
/**
* Copy configuration from an existing [JavaExec] task.
*/
fun copyFrom(task: JavaExec) {
for (dep in task.dependsOn) dependsOn(dep)
task.copyToFull(this)
if (!clientDebug) systemProperty("cctest.client", "")
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
workingDir(project.buildDir.resolve("gametest").resolve(name))
}
/**
* Only run tests with the given tags.
*/
fun tags(vararg tags: String) {
systemProperty("cctest.tags", tags.joinToString(","))
}
/**
* Write a file with the given contents before starting Minecraft. This may be useful for writing config files.
*/
fun withFileContents(path: Any, contents: Supplier<String>) {
val file = project.file(path).toPath()
doFirst {
Files.createDirectories(file.parent)
Files.writeString(file, contents.get())
}
}
/**
* Copy a file to the provided path before starting Minecraft. This copy only occurs if the file does not already
* exist.
*/
fun withFileFrom(path: Any, source: Supplier<File>) {
val file = project.file(path).toPath()
doFirst {
Files.createDirectories(file.parent)
if (!Files.exists(file)) Files.copy(source.get().toPath(), file)
}
}
@TaskAction
override fun exec() {
Files.createDirectories(workingDir.toPath())
fsOperations.delete { delete(workingDir.resolve("screenshots")) }
if (useFramebuffer) {
clientRunner.get().wrapClient(this) { super.exec() }
} else {
super.exec()
}
}
@get:Inject
protected abstract val fsOperations: FileSystemOperations
}
/**
* A service for [JavaExec] tasks which start Minecraft.
*
* Tasks may run `usesService(MinecraftRunnerService.get(gradle))` to ensure that only one Minecraft-related task runs
* at once.
*/
abstract class MinecraftRunnerService : BuildService<BuildServiceParameters.None> {
private val hasXvfb = lazy {
System.getProperty("os.name", "").equals("linux", ignoreCase = true) && ProcessHelpers.onPath("xvfb-run")
}
internal fun wrapClient(exec: JavaExec, run: () -> Unit) = when {
hasXvfb.value -> runXvfb(exec, run)
else -> run()
}
/**
* Run a program under Xvfb, preventing it spawning a window.
*/
private fun runXvfb(exec: JavaExec, run: () -> Unit) {
fun ProcessBuilder.startVerbose(): Process {
exec.logger.info("Running ${this.command()}")
return start()
}
CloseScope().use { scope ->
val dir = Files.createTempDirectory("cctweaked").toAbsolutePath()
scope.add { fsOperations.delete { delete(dir) } }
val authFile = Files.createTempFile(dir, "Xauthority", "").toAbsolutePath()
val cookie = StringBuilder().also {
for (i in 0..31) it.append("0123456789abcdef"[Random.nextInt(16)])
}.toString()
val xvfb =
ProcessBuilder("Xvfb", "-displayfd", "1", "-screen", "0", "640x480x24", "-nolisten", "tcp").also {
it.inheritIO()
it.environment()["XAUTHORITY"] = authFile.toString()
it.redirectOutput(ProcessBuilder.Redirect.PIPE)
}.startVerbose()
scope.add { xvfb.destroyForcibly().waitFor() }
val server = xvfb.inputReader().use { it.readLine().trim() }
exec.logger.info("Running at :$server (XAUTHORITY=$authFile.toA")
ProcessBuilder("xauth", "add", ":$server", ".", cookie).also {
it.inheritIO()
it.environment()["XAUTHORITY"] = authFile.toString()
}.startVerbose().waitForOrThrow("Failed to setup XAuthority file")
scope.add {
ProcessBuilder("xauth", "remove", ":$server").also {
it.inheritIO()
it.environment()["XAUTHORITY"] = authFile.toString()
}.startVerbose().waitFor()
}
// Wait a few seconds for Xvfb to start. Ugly, but identical to xvfb-run.
if (xvfb.waitFor(3, TimeUnit.SECONDS)) {
throw GradleException("Xvfb unexpectedly exited (with status code ${xvfb.exitValue()})")
}
exec.environment("XAUTHORITY", authFile.toString())
exec.environment("DISPLAY", ":$server")
run()
}
}
@get:Inject
protected abstract val fsOperations: FileSystemOperations
companion object {
fun get(gradle: Gradle): Provider<MinecraftRunnerService> =
gradle.sharedServices.registerIfAbsent("cc.tweaked.gradle.ClientJavaExec", MinecraftRunnerService::class.java) {
maxParallelUsages.set(1)
}
}
}

View File

@@ -1,35 +1,50 @@
package cc.tweaked.gradle
import org.codehaus.groovy.runtime.ProcessGroovyMethods
import org.gradle.api.GradleException
import java.io.BufferedReader
import java.io.IOException
import java.io.File
import java.io.InputStreamReader
import java.util.stream.Collectors
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 Runtime.getRuntime().exec(command, arrayOfNulls(0))
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)
if (process.waitFor() != 0) throw IOException("Command exited with a non-0 status")
process.waitForOrThrow("Failed to run command")
return result
}
fun captureLines(vararg command: String): List<String> {
return captureLines(startProcess(*command))
}
val process = startProcess(*command)
process.outputStream.close()
fun captureLines(process: Process): List<String> {
val out = BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
reader.lines().filter { it.isNotEmpty() }.collect(Collectors.toList())
reader.lines().filter { it.isNotEmpty() }.toList()
}
ProcessGroovyMethods.closeStreams(process)
if (process.waitFor() != 0) throw IOException("Command exited with a non-0 status")
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() }
}
}
internal fun Process.waitForOrThrow(message: String) {
val ret = waitFor()
if (ret != 0) throw GradleException("$message (exited with $ret)")
}

View File

@@ -0,0 +1,20 @@
package cc.tweaked.gradle
import org.gradle.api.tasks.SourceSet
data class SourceSetReference(
val sourceSet: SourceSet,
val classes: Boolean,
val external: Boolean,
) {
companion object {
/** A source set in the current project. */
fun internal(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = true, external = false)
/** A source set from another project. */
fun external(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = true, external = true)
/** A source set which is inlined into the current project. */
fun inline(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = false, external = false)
}
}

View File

@@ -0,0 +1,12 @@
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

@@ -17,7 +17,10 @@
<module name="TreeWalker">
<!-- Annotations -->
<module name="AnnotationLocation" />
<module name="AnnotationUseStyle" />
<module name="AnnotationUseStyle">
<!-- We want trailing commas on multiline arrays. -->
<property name="trailingArrayComma" value="ignore" />
</module>
<module name="MissingDeprecated" />
<module name="MissingOverride" />
@@ -26,17 +29,11 @@
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="ignored" />
</module>
<module name="LeftCurly">
<property name="option" value="nl" />
<!-- The defaults, minus lambdas. -->
<property name="tokens" value="ANNOTATION_DEF,CLASS_DEF,CTOR_DEF,ENUM_CONSTANT_DEF,ENUM_DEF,INTERFACE_DEF,LITERAL_CASE,LITERAL_CATCH,LITERAL_DEFAULT,LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,LITERAL_SWITCH,LITERAL_SYNCHRONIZED,LITERAL_TRY,LITERAL_WHILE,METHOD_DEF,OBJBLOCK,STATIC_INIT" />
</module>
<module name="LeftCurly" />
<module name="NeedBraces">
<property name="allowSingleLineStatement" value="true"/>
</module>
<module name="RightCurly">
<property name="option" value="alone" />
</module>
<module name="RightCurly" />
<!-- Class design. As if we've ever followed good practice here. -->
<module name="FinalClass" />
@@ -114,7 +111,7 @@
</module>
<module name="MethodTypeParameterName" />
<module name="PackageName">
<property name="format" value="^dan200\.computercraft(\.[a-z][a-z0-9]*)*" />
<property name="format" value="^(dan200\.computercraft|cc\.tweaked)(\.[a-z][a-z0-9]*)*" />
</module>
<module name="ParameterName" />
<module name="StaticVariableName">
@@ -131,18 +128,11 @@
<module name="MethodParamPad" />
<module name="NoLineWrap" />
<module name="NoWhitespaceAfter">
<property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP" />
<property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP,METHOD_REF" />
</module>
<module name="NoWhitespaceBefore" />
<!-- TODO: Decide on an OperatorWrap style. -->
<module name="ParenPad">
<property name="option" value="space" />
<property name="tokens" value="ANNOTATION,ANNOTATION_FIELD_DEF,CTOR_CALL,CTOR_DEF,ENUM_CONSTANT_DEF,LITERAL_CATCH,LITERAL_DO,LITERAL_FOR,LITERAL_IF,LITERAL_NEW,LITERAL_SWITCH,LITERAL_SYNCHRONIZED,LITERAL_WHILE,METHOD_CALL,METHOD_DEF,RESOURCE_SPECIFICATION,SUPER_CTOR_CALL,LAMBDA" />
</module>
<module name="ParenPad">
<property name="option" value="nospace" />
<property name="tokens" value="DOT,EXPR,QUESTION" />
</module>
<module name="ParenPad" />
<module name="SeparatorWrap">
<property name="option" value="eol" />
<property name="tokens" value="COMMA,SEMI,ELLIPSIS,ARRAY_DECLARATOR,RBRACK,METHOD_REF" />

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +0,0 @@
<code_scheme name="Project" version="173">
<JSON>
<option name="OBJECT_WRAPPING" value="1" />
<option name="ARRAY_WRAPPING" value="1" />
</JSON>
<JavaCodeStyleSettings>
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="JD_P_AT_EMPTY_LINES" value="false" />
<option name="JD_PRESERVE_LINE_FEEDS" value="true" />
</JavaCodeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="BRACE_STYLE" value="2" />
<option name="CLASS_BRACE_STYLE" value="2" />
<option name="METHOD_BRACE_STYLE" value="2" />
<option name="LAMBDA_BRACE_STYLE" value="5" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="SPACE_WITHIN_METHOD_CALL_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_METHOD_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_IF_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_WHILE_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_FOR_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_TRY_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_CATCH_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_SWITCH_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_SYNCHRONIZED_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="SPACE_BEFORE_IF_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_WHILE_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_TRY_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_CATCH_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_SWITCH_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="1" />
<option name="DOWHILE_BRACE_FORCE" value="1" />
<option name="WHILE_BRACE_FORCE" value="1" />
<option name="FOR_BRACE_FORCE" value="1" />
<option name="SPACE_WITHIN_ANNOTATION_PARENTHESES" value="true" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JSON">
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="SPACE_WITHIN_BRACKETS" value="true" />
<option name="SPACE_WITHIN_BRACES" value="true" />
<indentOptions>
<option name="INDENT_SIZE" value="4" />
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
</code_scheme>

View File

@@ -3,19 +3,20 @@ module: [kind=event] alarm
see: os.setAlarm To start an alarm.
---
The @{timer} event is fired when an alarm started with @{os.setAlarm} completes.
The @{alarm} event is fired when an alarm started with @{os.setAlarm} completes.
## Return Values
1. @{string}: The event name.
2. @{number}: The ID of the alarm that finished.
## Example
Starts a timer and then prints its ID:
Starts a timer and then waits for it to complete.
```lua
local alarmID = os.setAlarm(os.time() + 0.05)
local alarm_id = os.setAlarm(os.time() + 0.05)
local event, id
repeat
event, id = os.pullEvent("alarm")
until id == alarmID
until id == alarm_id
print("Alarm with ID " .. id .. " was fired")
```

View File

@@ -3,7 +3,7 @@ module: [kind=event] char
see: key To listen to any key press.
---
The @{char} event is fired when a character is _typed_ on the keyboard.
The @{char} event is fired when a character is typed on the keyboard.
The @{char} event is different to a key press. Sometimes multiple key presses may result in one character being
typed (for instance, on some European keyboards). Similarly, some keys (e.g. <kbd>Ctrl</kbd>) do not have any
@@ -16,9 +16,10 @@ corresponding character. The @{key} should be used if you want to listen to key
## Example
Prints each character the user presses:
```lua
while true do
local event, character = os.pullEvent("char")
print(character .. " was pressed.")
local event, character = os.pullEvent("char")
print(character .. " was pressed.")
end
```

View File

@@ -6,7 +6,7 @@ The @{computer_command} event is fired when the `/computercraft queue` command i
## Return Values
1. @{string}: The event name.
... @{string}: The arguments passed to the command.
2. @{string}<abbr title="Variable number of arguments">&hellip;</abbr>: The arguments passed to the command.
## Example
Prints the contents of messages sent:

View File

@@ -5,9 +5,9 @@ since: 1.101.0
The @{file_transfer} event is queued when a user drags-and-drops a file on an open computer.
This event contains a single argument, that in turn has a single method @{TransferredFiles.getFiles|getFiles}. This
returns the list of files that are being transferred. Each file is a @{fs.BinaryReadHandle|binary file handle} with an
additional @{TransferredFile.getName|getName} method.
This event contains a single argument of type @{TransferredFiles}, which can be used to @{TransferredFiles.getFiles|get
the files to be transferred}. Each file returned is a @{fs.BinaryReadHandle|binary file handle} with an additional
@{TransferredFile.getName|getName} method.
## Return values
1. @{string}: The event name

View File

@@ -11,4 +11,4 @@ This event is normally handled inside @{http.checkURL}, but it can still be seen
1. @{string}: The event name.
2. @{string}: The URL requested to be checked.
3. @{boolean}: Whether the check succeeded.
4. @{string|nil}: If the check failed, a reason explaining why the check failed.
4. <span class="type">@{string}|@{nil}</span>: If the check failed, a reason explaining why the check failed.

View File

@@ -11,7 +11,8 @@ This event is normally handled inside @{http.get} and @{http.post}, but it can s
1. @{string}: The event name.
2. @{string}: The URL of the site requested.
3. @{string}: An error describing the failure.
4. @{http.Response|nil}: A response handle if the connection succeeded, but the server's response indicated failure.
4. <span class="type">@{http.Response}|@{nil}</span>: A response handle if the connection succeeded, but the server's
response indicated failure.
## Example
Prints an error why the website cannot be contacted:

View File

@@ -10,7 +10,7 @@ This event is normally handled inside @{http.get} and @{http.post}, but it can s
## Return Values
1. @{string}: The event name.
2. @{string}: The URL of the site requested.
3. @{http.Response}: The handle for the response text.
3. @{http.Response}: The successful HTTP response.
## Example
Prints the content of a website (this may fail if the request fails):

View File

@@ -10,7 +10,7 @@ The @{modem_message} event is fired when a message is received on an open channe
3. @{number}: The channel that the message was sent on.
4. @{number}: The reply channel set by the sender.
5. @{any}: The message as sent by the sender.
6. @{number}: The distance between the sender and the receiver, in blocks.
6. <span class="type">@{number}|@{nil}</span>: The distance between the sender and the receiver in blocks, or @{nil} if the message was sent between dimensions.
## Example
Wraps a @{modem} peripheral, opens channel 0 for listening, and prints all received messages.
@@ -20,7 +20,9 @@ local modem = peripheral.find("modem") or error("No modem attached", 0)
modem.open(0)
while true do
local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
print(("Message received on side %s on channel %d (reply to %d) from %f blocks away with message %s"):format(side, channel, replyChannel, distance, tostring(message)))
local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
print(("Message received on side %s on channel %d (reply to %d) from %f blocks away with message %s"):format(
side, channel, replyChannel, distance, tostring(message)
))
end
```

View File

@@ -6,10 +6,11 @@ The @{monitor_resize} event is fired when an adjacent or networked monitor's siz
## Return Values
1. @{string}: The event name.
2. @{string}: The side or network ID of the monitor that resized.
2. @{string}: The side or network ID of the monitor that was resized.
## Example
Prints a message when a monitor is resized:
```lua
while true do
local event, side = os.pullEvent("monitor_resize")

View File

@@ -14,7 +14,7 @@ This event is usually handled by @{rednet.receive}, but it can also be pulled ma
1. @{string}: The event name.
2. @{number}: The ID of the sending computer.
3. @{any}: The message sent.
4. @{string|nil}: The protocol of the message, if provided.
4. <span class="type">@{string}|@{nil}</span>: The protocol of the message, if provided.
## Example
Prints a message when one is sent:

View File

@@ -4,6 +4,9 @@ module: [kind=event] redstone
The @{event!redstone} event is fired whenever any redstone inputs on the computer change.
## Return values
1. @{string}: The event name.
## Example
Prints a message when a redstone input changes:
```lua

View File

@@ -10,7 +10,7 @@ The @{task_complete} event is fired when an asynchronous task completes. This is
2. @{number}: The ID of the task that completed.
3. @{boolean}: Whether the command succeeded.
4. @{string}: If the command failed, an error message explaining the failure. (This is not present if the command succeeded.)
...: Any parameters returned from the command.
5. <abbr title="Variable number of arguments">&hellip;</abbr>: Any parameters returned from the command.
## Example
Prints the results of an asynchronous command:

View File

@@ -9,8 +9,12 @@ The @{term_resize} event is fired when the main terminal is resized. For instanc
When this event fires, some parts of the terminal may have been moved or deleted. Simple terminal programs (those
not using @{term.setCursorPos}) can ignore this event, but more complex GUI programs should redraw the entire screen.
## Return values
1. @{string}: The event name.
## Example
Prints :
Print a message each time the terminal is resized.
```lua
while true do
os.pullEvent("term_resize")

View File

@@ -8,6 +8,9 @@ This event is normally handled by @{os.pullEvent}, and will not be returned. How
@{terminate} will be sent even when a filter is provided to @{os.pullEventRaw}. When using @{os.pullEventRaw} with a filter, make sure to check that the event is not @{terminate}.
## Return values
1. @{string}: The event name.
## Example
Prints a message when Ctrl-T is held:
```lua

View File

@@ -10,12 +10,12 @@ The @{timer} event is fired when a timer started with @{os.startTimer} completes
2. @{number}: The ID of the timer that finished.
## Example
Starts a timer and then prints its ID:
Start and wait for a timer to finish.
```lua
local timerID = os.startTimer(2)
local timer_id = os.startTimer(2)
local event, id
repeat
event, id = os.pullEvent("timer")
until id == timerID
until id == timer_id
print("Timer with ID " .. id .. " was fired")
```

View File

@@ -4,6 +4,9 @@ module: [kind=event] turtle_inventory
The @{turtle_inventory} event is fired when a turtle's inventory is changed.
## Return values
1. @{string}: The event name.
## Example
Prints a message when the inventory is changed:
```lua

View File

@@ -25,7 +25,7 @@ single-player or multiplayer. Look for lines that look like this:
```
On 1.95.0 and later, this will be a single entry with `host = "$private"`. On earlier versions, this will be a number of
`[[http.rules]]` with various IP addresses. You will want to remove all of the `[[http.rules]]` entires that have
`[[http.rules]]` with various IP addresses. You will want to remove all of the `[[http.rules]]` entries that have
`action = "deny"`. Then save the file and relaunch Minecraft (Server).
Here's what it should look like after removing:
@@ -54,7 +54,7 @@ like this:
```toml
#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers.
#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
#You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
blacklist = ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"]
```
@@ -65,7 +65,7 @@ Here's what it should look like after removing:
```toml
#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers.
#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
#You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
blacklist = []
```

View File

@@ -1,64 +0,0 @@
--- @module fs
--- Returns true if a path is mounted to the parent filesystem.
--
-- The root filesystem "/" is considered a mount, along with disk folders and
-- the rom folder. Other programs (such as network shares) can exstend this to
-- make other mount types by correctly assigning their return value for getDrive.
--
-- @tparam string path The path to check.
-- @treturn boolean If the path is mounted, rather than a normal file/folder.
-- @throws If the path does not exist.
-- @see getDrive
-- @since 1.87.0
function isDriveRoot(path) end
--[[- Provides completion for a file or directory name, suitable for use with
@{_G.read}.
When a directory is a possible candidate for completion, two entries are
included - one with a trailing slash (indicating that entries within this
directory exist) and one without it (meaning this entry is an immediate
completion candidate). `include_dirs` can be set to @{false} to only include
those with a trailing slash.
@tparam[1] string path The path to complete.
@tparam[1] string location The location where paths are resolved from.
@tparam[1,opt=true] boolean include_files When @{false}, only directories will
be included in the returned list.
@tparam[1,opt=true] boolean include_dirs When @{false}, "raw" directories will
not be included in the returned list.
@tparam[2] string path The path to complete.
@tparam[2] string location The location where paths are resolved from.
@tparam[2] {
include_dirs? = boolean, include_files? = boolean,
include_hidden? = boolean
} options
This table form is an expanded version of the previous syntax. The
`include_files` and `include_dirs` arguments from above are passed in as fields.
This table also accepts the following options:
- `include_hidden`: Whether to include hidden files (those starting with `.`)
by default. They will still be shown when typing a `.`.
@treturn { string... } A list of possible completion candidates.
@since 1.74
@changed 1.101.0
@usage Complete files in the root directory.
read(nil, nil, function(str)
return fs.complete(str, "", true, false)
end)
@usage Complete files in the root directory, hiding hidden files by default.
read(nil, nil, function(str)
return fs.complete(str, "", {
include_files = true,
include_dirs = false,
included_hidden = false,
})
end)
]]
function complete(path, location, include_files, include_dirs) end

View File

@@ -1,177 +0,0 @@
--- Make HTTP requests, sending and receiving data to a remote web server.
--
-- @module http
-- @since 1.1
-- @see local_ips To allow accessing servers running on your local network.
--- Asynchronously make a HTTP request to the given url.
--
-- This returns immediately, a @{http_success} or @{http_failure} will be queued
-- once the request has completed.
--
-- @tparam string url The url to request
-- @tparam[opt] string body An optional string containing the body of the
-- request. If specified, a `POST` request will be made instead.
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
-- of this request.
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
-- the body will not be UTF-8 encoded, and the received response will not be
-- decoded.
--
-- @tparam[2] {
-- url = string, body? = string, headers? = { [string] = string },
-- binary? = boolean, method? = string, redirect? = boolean,
-- } request Options for the request.
--
-- This table form is an expanded version of the previous syntax. All arguments
-- from above are passed in as fields instead (for instance,
-- `http.request("https://example.com")` becomes `http.request { url =
-- "https://example.com" }`).
--
-- This table also accepts several additional options:
--
-- - `method`: Which HTTP method to use, for instance `"PATCH"` or `"DELETE"`.
-- - `redirect`: Whether to follow HTTP redirects. Defaults to true.
--
-- @see http.get For a synchronous way to make GET requests.
-- @see http.post For a synchronous way to make POST requests.
--
-- @changed 1.63 Added argument for headers.
-- @changed 1.80pr1 Added argument for binary handles.
-- @changed 1.80pr1.6 Added support for table argument.
-- @changed 1.86.0 Added PATCH and TRACE methods.
function request(...) end
--- Make a HTTP GET request to the given url.
--
-- @tparam string url The url to request
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
-- of this request.
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
-- the body will not be UTF-8 encoded, and the received response will not be
-- decoded.
--
-- @tparam[2] {
-- url = string, headers? = { [string] = string },
-- binary? = boolean, method? = string, redirect? = boolean,
-- } request Options for the request. See @{http.request} for details on how
-- these options behave.
--
-- @treturn Response The resulting http response, which can be read from.
-- @treturn[2] nil When the http request failed, such as in the event of a 404
-- error or connection timeout.
-- @treturn string A message detailing why the request failed.
-- @treturn Response|nil The failing http response, if available.
--
-- @changed 1.63 Added argument for headers.
-- @changed 1.80pr1 Response handles are now returned on error if available.
-- @changed 1.80pr1 Added argument for binary handles.
-- @changed 1.80pr1.6 Added support for table argument.
-- @changed 1.86.0 Added PATCH and TRACE methods.
--
-- @usage Make a request to [example.tweaked.cc](https://example.tweaked.cc),
-- and print the returned page.
-- ```lua
-- local request = http.get("https://example.tweaked.cc")
-- print(request.readAll())
-- -- => HTTP is working!
-- request.close()
-- ```
function get(...) end
--- Make a HTTP POST request to the given url.
--
-- @tparam string url The url to request
-- @tparam string body The body of the POST request.
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
-- of this request.
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
-- the body will not be UTF-8 encoded, and the received response will not be
-- decoded.
--
-- @tparam[2] {
-- url = string, body? = string, headers? = { [string] = string },
-- binary? = boolean, method? = string, redirect? = boolean,
-- } request Options for the request. See @{http.request} for details on how
-- these options behave.
--
-- @treturn Response The resulting http response, which can be read from.
-- @treturn[2] nil When the http request failed, such as in the event of a 404
-- error or connection timeout.
-- @treturn string A message detailing why the request failed.
-- @treturn Response|nil The failing http response, if available.
--
-- @since 1.31
-- @changed 1.63 Added argument for headers.
-- @changed 1.80pr1 Response handles are now returned on error if available.
-- @changed 1.80pr1 Added argument for binary handles.
-- @changed 1.80pr1.6 Added support for table argument.
-- @changed 1.86.0 Added PATCH and TRACE methods.
function post(...) end
--- Asynchronously determine whether a URL can be requested.
--
-- If this returns `true`, one should also listen for @{http_check} which will
-- container further information about whether the URL is allowed or not.
--
-- @tparam string url The URL to check.
-- @treturn true When this url is not invalid. This does not imply that it is
-- allowed - see the comment above.
-- @treturn[2] false When this url is invalid.
-- @treturn string A reason why this URL is not valid (for instance, if it is
-- malformed, or blocked).
--
-- @see http.checkURL For a synchronous version.
function checkURLAsync(url) end
--- Determine whether a URL can be requested.
--
-- If this returns `true`, one should also listen for @{http_check} which will
-- container further information about whether the URL is allowed or not.
--
-- @tparam string url The URL to check.
-- @treturn true When this url is valid and can be requested via @{http.request}.
-- @treturn[2] false When this url is invalid.
-- @treturn string A reason why this URL is not valid (for instance, if it is
-- malformed, or blocked).
--
-- @see http.checkURLAsync For an asynchronous version.
--
-- @usage
-- ```lua
-- print(http.checkURL("https://example.tweaked.cc/"))
-- -- => true
-- print(http.checkURL("http://localhost/"))
-- -- => false Domain not permitted
-- print(http.checkURL("not a url"))
-- -- => false URL malformed
-- ```
function checkURL(url) end
--- Open a websocket.
--
-- @tparam string url The websocket url to connect to. This should have the
-- `ws://` or `wss://` protocol.
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
-- of the initial websocket connection.
--
-- @treturn Websocket The websocket connection.
-- @treturn[2] false If the websocket connection failed.
-- @treturn string An error message describing why the connection failed.
-- @since 1.80pr1.1
-- @changed 1.80pr1.3 No longer asynchronous.
-- @changed 1.95.3 Added User-Agent to default headers.
function websocket(url, headers) end
--- Asynchronously open a websocket.
--
-- This returns immediately, a @{websocket_success} or @{websocket_failure}
-- will be queued once the request has completed.
--
-- @tparam string url The websocket url to connect to. This should have the
-- `ws://` or `wss://` protocol.
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
-- of the initial websocket connection.
-- @since 1.80pr1.3
-- @changed 1.95.3 Added User-Agent to default headers.
function websocketAsync(url, headers) end

View File

@@ -5,7 +5,8 @@ kotlin.stdlib.default.dependency=false
kotlin.jvm.target.validation.mode=error
# Mod properties
modVersion=1.101.0
isUnstable=false
modVersion=1.103.1
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.19.2
mcVersion=1.19.3

View File

@@ -2,70 +2,153 @@
# Minecraft
# MC version is specified in gradle.properties, as we need that in settings.gradle.
forge = "43.1.1"
parchment = "2022.10.16"
fabric-api = "0.68.1+1.19.3"
fabric-loader = "0.14.11"
forge = "44.1.0"
forgeSpi = "6.0.0"
mixin = "0.8.5"
parchment = "2022.11.27"
parchmentMc = "1.19.2"
# Normal dependencies
asm = "9.3"
autoService = "1.0.1"
cobalt = { strictly = "[0.5.8,0.6.0)", prefer = "0.5.8" }
checkerFramework = "3.12.0"
cobalt = "0.6.0"
fastutil = "8.5.9"
guava = "31.1-jre"
jetbrainsAnnotations = "23.0.0"
kotlin = "1.7.10"
kotlin-coroutines = "1.6.0"
jsr305 = "3.0.2"
kotlin = "1.8.0"
kotlin-coroutines = "1.6.4"
netty = "4.1.82.Final"
nightConfig = "3.6.5"
slf4j = "1.7.36"
# Minecraft mods
forgeConfig = "5.0.4"
iris = "1.19.3-v1.4.6"
jei = "11.3.0.262"
modmenu = "5.0.1"
oculus = "1.2.5"
rei = "10.0.578"
rubidium = "0.6.1"
sodium = "mc1.19.3-0.4.6"
# Testing
byteBuddy = "1.12.19"
hamcrest = "2.2"
jqwik = "1.7.0"
junit = "5.9.1"
# Build tools
cctJavadoc = "1.5.2"
cctJavadoc = "1.6.0"
checkstyle = "10.3.4"
curseForgeGradle = "1.0.11"
errorProne-core = "2.14.0"
errorProne-plugin = "2.0.2"
fabric-loom = "1.0-SNAPSHOT"
forgeGradle = "5.1.+"
githubRelease = "2.2.12"
illuaminate = "0.1.0-7-g2a5a89c"
ideaExt = "1.1.6"
illuaminate = "0.1.0-13-g689d73d"
librarian = "1.+"
minotaur = "2.+"
mixinGradle = "0.7.+"
nullAway = "0.9.9"
quiltflower = "1.7.3"
shadow = "7.1.2"
spotless = "6.8.0"
taskTree = "2.1.0"
vanillaGradle = "0.2.1-SNAPSHOT"
[libraries]
# Normal dependencies
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
checkerFramework = { module = "org.checkerframework:checker-qual", version.ref = "checkerFramework" }
cobalt = { module = "org.squiddev:Cobalt", version.ref = "cobalt" }
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" }
kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
netty-http = { module = "io.netty:netty-codec-http", 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" }
# Minecraft mods
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
forgeConfig = { module = "fuzs.forgeconfigapiport:forgeconfigapiport-fabric", version.ref = "forgeConfig" }
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
jei-api = { module = "mezz.jei:jei-1.19.2-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.19.2-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.19.2-forge", version.ref = "jei" }
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
rei-api = { module = "me.shedaniel:RoughlyEnoughItems-api", version.ref = "rei" }
rei-builtin = { module = "me.shedaniel:RoughlyEnoughItems-default-plugin", version.ref = "rei" }
rei-fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" }
rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
# Testing
byteBuddyAgent = { module ="net.bytebuddy:byte-buddy-agent", version.ref = "byteBuddy" }
byteBuddy = { module ="net.bytebuddy:byte-buddy", version.ref = "byteBuddy" }
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
jqwik-api = { module = "net.jqwik:jqwik-api", version.ref = "jqwik" }
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" }
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
# Build tools
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
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" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
quiltflower = { module = "io.github.juuxel:loom-quiltflower", version.ref = "quiltflower" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
vanillaGradle = { module = "org.spongepowered:vanillagradle", version.ref = "vanillaGradle" }
[plugins]
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
curseForgeGradle = { id = "net.darkhax.curseforgegradle", version.ref = "curseForgeGradle" }
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" }
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExt" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" }
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
[bundles]
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
# Minecraft
externalMods-common = ["jei-api", "forgeConfig", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["oculus", "jei-api"]
externalMods-forge-runtime = []
externalMods-fabric = ["fabric-loader", "fabric-api", "forgeConfig", "nightConfig-core", "nightConfig-toml"]
externalMods-fabric-compile = ["iris", "jei-api", "rei-api", "rei-builtin"]
externalMods-fabric-runtime = ["modmenu"]
# Testing
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]

View File

@@ -2,25 +2,26 @@
(sources
/doc/
/build/docs/luaJavadoc/
/src/main/resources/*/computercraft/lua/bios.lua
/src/main/resources/*/computercraft/lua/rom/
/src/test/resources/test-rom
/src/web/mount)
/projects/forge/build/docs/luaJavadoc/
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/
/projects/core/src/test/resources/test-rom
/projects/web/src/mount)
(doc
(destination build/illuaminate)
; Also defined in projects/web/build.gradle.kts
(destination /projects/web/build/illuaminate)
(index doc/index.md)
(site
(title "CC: Tweaked")
(logo src/main/resources/pack.png)
(logo projects/common/src/main/resources/pack.png)
(url https://tweaked.cc/)
(source-link https://github.com/cc-tweaked/CC-Tweaked/blob/${commit}/${path}#L${line})
(styles src/web/styles.css)
(scripts build/rollup/index.js)
(styles /projects/web/src/styles.css)
(scripts /projects/web/build/rollup/index.js)
(head doc/head.html))
(module-kinds
@@ -32,15 +33,15 @@
(library-path
/doc/stub/
/build/docs/luaJavadoc/
/projects/forge/build/docs/luaJavadoc/
/src/main/resources/*/computercraft/lua/rom/apis/
/src/main/resources/*/computercraft/lua/rom/apis/command/
/src/main/resources/*/computercraft/lua/rom/apis/turtle/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/command/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/
/src/main/resources/*/computercraft/lua/rom/modules/main/
/src/main/resources/*/computercraft/lua/rom/modules/command/
/src/main/resources/*/computercraft/lua/rom/modules/turtle/))
/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/
/projects/core/src/main/resources/data/computercraft/lua/rom/modules/command/
/projects/core/src/main/resources/data/computercraft/lua/rom/modules/turtle/))
(at /
(linters
@@ -80,37 +81,37 @@
;; We disable the unused global linter in bios.lua and the APIs. In the future
;; hopefully we'll get illuaminate to handle this.
(at
(/src/main/resources/*/computercraft/lua/bios.lua
/src/main/resources/*/computercraft/lua/rom/apis/)
(/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/)
(linters -var:unused-global)
(lint (allow-toplevel-global true)))
;; Silence some variable warnings in documentation stubs.
(at (/doc/stub/ /build/docs/luaJavadoc/)
(at (/doc/stub/ /projects/forge/build/docs/luaJavadoc/)
(linters -var:unused-global)
(lint (allow-toplevel-global true)))
;; Suppress warnings for currently undocumented modules.
(at
(; Lua APIs
/src/main/resources/*/computercraft/lua/rom/apis/io.lua
/src/main/resources/*/computercraft/lua/rom/apis/window.lua)
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/io.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/window.lua)
(linters -doc:undocumented -doc:undocumented-arg -doc:undocumented-return))
;; Suppress warnings for various APIs using its own deprecated members.
(at
(/src/main/resources/*/computercraft/lua/bios.lua
/src/main/resources/*/computercraft/lua/rom/apis/turtle/turtle.lua)
(/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua)
(linters -var:deprecated))
(at /src/test/resources/test-rom
(at /projects/core/src/test/resources/test-rom
; We should still be able to test deprecated members.
(linters -var:deprecated)
(lint
(globals
:max sleep write
cct_test describe expect howlci fail it pending stub)))
cct_test describe expect howlci fail it pending stub before_each)))
(at /src/web/mount/expr_template.lua (lint (globals :max __expr__)))
(at /projects/web/src/mount/expr_template.lua (lint (globals :max __expr__)))

159
projects/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,159 @@
# Architecture
CC: Tweaked has a rather complex project layout, as there's several use-cases we want to support (multiple mod loaders,
usable outside of Minecraft). As such, it can be tricky to understand how the code is structured and how the various
sub-projects interact. This document provides a high-level overview of the entire mod.
## Project Outline
CC: Tweaked is split into 4 primary modules (`core`, `common`, `fabric`, `forge`). These themselves are then split into
a public API (i.e `core-api`) and the actual implementation (i.e. `core`).
- `core`: This contains the core "computer" part of ComputerCraft, such as the Lua VM, filesystem and builtin APIs.
This is also where the Lua ROM is located (`projects/core/src/main/resources/data/computercraft/lua`). Notably this
project does _not_ depend on Minecraft, making it possible to use it in emulators and other tooling.
- `common`: This contains all non mod-loader-specific Minecraft code. This is where computers, turtles and peripherals
are defined (and everything else Minecraft-related!).
This project is separates client code into its own separate source set (suitably named `client`). This helps us
ensure that server code can never reference client-only code (such as LWJGL).
- `forge` and `fabric`: These contain any mod-loader specific code.
When we need to call loader-specific code from our own code (for instance, sending network messages or firing
loader-specific events), we use a `PlatformHelper` interface (defined in
`projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java`). This abstracts over most
loader-specific code we need to use, and is then implemented by each mod-loader-specific project. The concrete
implementation is then loaded with Java's [`ServiceLoader`][ServiceLoader], in a design based on [jaredlll08's
multi-loader template][MultiLoader-Template]. We use a similar system for communicating between the API and its
implementation.
```mermaid
flowchart LR
subgraph Common
platform(PlatformHelper)
impl[AbstractComputerCraftAPI]
end
subgraph API
api(ComputerCraft API) --> impl
end
subgraph Forge[Forge]
platform --> forgePlatform[PlatformHelperImpl]
impl -.-> forgeImpl[ComputerCraftAPIImpl]
end
subgraph Fabric
platform --> fabricPlatform[PlatformHelperImpl]
impl -.-> fabricImpl[ComputerCraftAPIImpl]
end
```
Note the `PlatformHelper` is only used when calling from our code into loader-specific code. While we use this to _fire_
events, we do not use it to _subscribe_ to events. For that we just subscribe to the events in the loader-specific
project, and then dispatch to the common `CommonHooks` (for shared code) and `ClientHooks` (for client-specific code).
You may notice there's a couple of other, smaller modules in the codebase. These you can probably ignore, but are worth
mentioning:
- `lints`: This defines an [ErrorProne] plugin which adds a couple of compile-time checks to our code. This is what
enforces that no client-specific code is used inside the `main` source set (and a couple of other things!).
- `web`: This contains the additional tooling for building [the documentation website][tweaked.cc], such as support for
rendering recipes
- `buildSrc` (in the base directory, not in `projects/`): This contains any build logic shared between modules. For
instance, `cc-tweaked.java-convention.gradle.kts` sets up the defaults for Java that we use across the whole project.
> **Note**
> The Forge and Fabric modules (and their API counterparts) depend on the common modules. However, in order to correctly
> process mixins we need to compile the common code along with the Forge/Fabric code. This leads to a slightly strange
> build process:
>
> - In your IDE, Forge/Fabric depend on the common as normal.
> - When building via Gradle, the common code is compiled alongside Forge/Fabric.
>
> You shouldn't need to worry about this - it should all be set up automatically - but hopefully explains a little bit
> why our Gradle scripts are slightly odd!
## Testing
CC: Tweaked has a small (though growing!) test suite to ensure various features behave correctly. Most tests are written
in Java using [JUnit], though we also make use of [jqwik] for property testing.
### Test Fixtures
Some projects define an additional `testFixtures` folder alongside their main `test` code (i.e.
`projects/core/src/testFixtures`). This source set contains test-related code which might be consumed in dependent
projects. For instance, core's test fixtures defines additional [Hamcrest] matchers, which are used in both `core` and
`common`'s test suite.
Test fixtures may also define [Test Interfaces]. This is a pattern for writing tests to ensure that an implementation
obeys its interface's contract. For instance, we might have a `ListContract` test, which asserts an abstract list
behaves as expected:
```java
interface ListContract<T extends List<Integer>> {
T newList();
@Test
default void testAddInsert() {
var list = newList();
assertTrue(list.add(123));
assertTrue(list.contains(123));
}
}
```
We can then use this interface to create tests for a specific implementation:
```java
class ArrayListTest implements ListContract<ArrayList<Integer>> {
@Override public ArrayList<Integer> newList() { return new ArrayList<>(); }
}
```
This is especially useful when testing `PlatformHelper` and other mod loader abstractions.
### Lua tests
While the majority of CC: Tweaked is written in Java, a significant portion of the code is written in Lua. As such, it's
also useful to test that.
This is done by starting a Lua VM with all of ComputerCraft's APIs loaded, then starting a custom test framework
(`mcfly.lua`). This test framework discovers tests and sends them back to the Java side. These are turned into JUnit
tests which are then in turn run on the computer again. This allows the tests to integrate with existing Java testing
tooling (for instance, XML test reports and IDE integration).
There's a slightly more detailed description of the process at `ComputerTestDelegate.java`.
### Game tests
CC: Tweaked also runs several tests in-game using Minecraft's [gametest framework][mc-test]. These work by starting
a Minecraft server and then, for each test, spawning a structure and then interacting with the blocks inside the
structure, asserting they behave as expected.
Unlike most of our other tests, these are written in Kotlin. We make extensive use of [extension methods] to augment
vanilla's own test classes, which helps give a more consistent feel to the API.
Each test works by defining a sequence of steps. Each step can either run an action (`thenExecute`), sleep for a period
(`thenIdle`) or sleep until a condition is met (`thenWaitUntil`).
```kotlin
fun Some_test(context: GameTestHelper) = context.sequence {
thenExecute { context.setBlock(BlockPos(2, 2, 2), Blocks.AIR) }
thenIdle(4)
thenExecute { context.assertBlockHas(lamp, RedstoneLampBlock.LIT, false, "Lamp should not be lit") }
}
```
Some tests need to use Lua APIs from a computer, such as when testing `turtle.dig`. In order to do this, we install
a custom "Lua" runtime (see `ManagedComputers.kt`) which actually runs Java functions. Tests can then enqueue a function
to run on a particular computer and then wait for it to finish.
While the internals of this is quite complex, it ends up being a much nicer workflow than writing parts of the test in
Lua. It also ends up being much more efficient, which is important when running a dozen tests at once!
[MultiLoader-Template]: https://github.com/jaredlll08/MultiLoader-Template/ "MultiLoader-Template - A template for a Forge + Fabric project setup using a Common source set."
[ServiceLoader]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html "ServiceLoader (Java SE 17 and JDK 17)"
[ErrorProne]: https://errorprone.info/ "ErrorProne"
[tweaked.cc]: https://tweaked.cc "CC: Tweaked"
[JUnit]: https://junit.org/junit5/ "JUnit 5"
[jqwik]: https://jqwik.net/
[Hamcrest]: https://hamcrest.org/JavaHamcrest/ "Java Hamcrest"
[Test Interfaces]: https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-interfaces-and-default-methods
[mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg "Testing Minecraft in Minecraft on YouTube"
[extension methods]: https://kotlinlang.org/docs/extensions.html "Extensions | Kotlin"

View File

@@ -0,0 +1,20 @@
plugins {
id("cc-tweaked.java-convention")
id("cc-tweaked.publishing")
id("cc-tweaked.vanilla")
}
java {
withJavadocJar()
}
dependencies {
api(project(":core-api"))
}
tasks.javadoc {
include("dan200/computercraft/api/**/*.java")
// 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

@@ -9,33 +9,27 @@ import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import javax.annotation.Nonnull;
public final class ComputerCraftAPIClient
{
private ComputerCraftAPIClient()
{
public final class ComputerCraftAPIClient {
private ComputerCraftAPIClient() {
}
/**
* Register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
* <p>
* This may be called at any point after registry creation, though it is recommended to call it within {@link FMLClientSetupEvent}.
* This may be called at any point after registry creation, though it is recommended to call it within your client
* setup step.
*
* @param serialiser The turtle upgrade serialiser.
* @param modeller The upgrade modeller.
* @param <T> The type of the turtle upgrade.
*/
public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller( @Nonnull TurtleUpgradeSerialiser<T> serialiser, @Nonnull TurtleUpgradeModeller<T> modeller )
{
getInstance().registerTurtleUpgradeModeller( serialiser, modeller );
public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
getInstance().registerTurtleUpgradeModeller(serialiser, modeller);
}
@Nonnull
private static ComputerCraftAPIClientService getInstance()
{
private static ComputerCraftAPIClientService getInstance() {
return ComputerCraftAPIClientService.get();
}
}

View File

@@ -0,0 +1,57 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.client;
import com.mojang.math.Transformation;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import java.util.Objects;
/**
* A model to render, combined with a transformation matrix to apply.
*/
public final class TransformedModel {
private final BakedModel model;
private final Transformation matrix;
public TransformedModel(BakedModel model, Transformation matrix) {
this.model = Objects.requireNonNull(model);
this.matrix = Objects.requireNonNull(matrix);
}
public TransformedModel(BakedModel model) {
this.model = Objects.requireNonNull(model);
matrix = Transformation.identity();
}
public static TransformedModel of(ModelResourceLocation location) {
var modelManager = Minecraft.getInstance().getModelManager();
return new TransformedModel(modelManager.getModel(location));
}
public static TransformedModel of(ResourceLocation location) {
var modelManager = Minecraft.getInstance().getModelManager();
return new TransformedModel(ClientPlatformHelper.get().getModel(modelManager, location));
}
public static TransformedModel of(ItemStack item, Transformation transform) {
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(item);
return new TransformedModel(model, transform);
}
public BakedModel getModel() {
return model;
}
public Transformation getMatrix() {
return matrix;
}
}

View File

@@ -14,7 +14,6 @@ import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
@@ -23,8 +22,7 @@ import javax.annotation.Nullable;
* @param <T> The type of turtle upgrade this modeller applies to.
* @see ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller) To register a modeller.
*/
public interface TurtleUpgradeModeller<T extends ITurtleUpgrade>
{
public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
/**
* Obtain the model to be used when rendering a turtle peripheral.
* <p>
@@ -35,8 +33,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade>
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade.
*/
@Nonnull
TransformedModel getModel( @Nonnull T upgrade, @Nullable ITurtleAccess turtle, @Nonnull TurtleSide side );
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
/**
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
@@ -48,9 +45,8 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade>
* @param <T> The type of the turtle upgrade.
* @return The constructed modeller.
*/
@SuppressWarnings( "unchecked" )
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem()
{
@SuppressWarnings("unchecked")
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.FLAT_ITEM;
}
@@ -62,9 +58,8 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade>
* @param <T> The type of the turtle upgrade.
* @return The constructed modeller.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided( ModelResourceLocation left, ModelResourceLocation right )
{
return ( upgrade, turtle, side ) -> TransformedModel.of( side == TurtleSide.LEFT ? left : right );
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
}
/**
@@ -75,8 +70,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade>
* @param <T> The type of the turtle upgrade.
* @return The constructed modeller.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided( ResourceLocation left, ResourceLocation right )
{
return ( upgrade, turtle, side ) -> TransformedModel.of( side == TurtleSide.LEFT ? left : right );
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
}
}

View File

@@ -5,27 +5,28 @@
*/
package dan200.computercraft.api.client.turtle;
import com.mojang.math.Matrix4f;
import com.mojang.math.Transformation;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import org.joml.Matrix4f;
class TurtleUpgradeModellers
{
private static final Transformation leftTransform = getMatrixFor( -0.40625f );
private static final Transformation rightTransform = getMatrixFor( 0.40625f );
class TurtleUpgradeModellers {
private static final Transformation leftTransform = getMatrixFor(-0.40625f);
private static final Transformation rightTransform = getMatrixFor(0.40625f);
private static Transformation getMatrixFor( float offset )
{
return new Transformation( new Matrix4f( new float[] {
private static Transformation getMatrixFor(float offset) {
var matrix = new Matrix4f();
matrix.set(new float[]{
0.0f, 0.0f, -1.0f, 1.0f + offset,
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f,
} ) );
});
matrix.transpose();
return new Transformation(matrix);
}
static final TurtleUpgradeModeller<ITurtleUpgrade> FLAT_ITEM = ( upgrade, turtle, side ) ->
TransformedModel.of( upgrade.getCraftingItem(), side == TurtleSide.LEFT ? leftTransform : rightTransform );
static final TurtleUpgradeModeller<ITurtleUpgrade> FLAT_ITEM = (upgrade, turtle, side) ->
TransformedModel.of(upgrade.getCraftingItem(), side == TurtleSide.LEFT ? leftTransform : rightTransform);
}

View File

@@ -0,0 +1,46 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl.client;
import dan200.computercraft.impl.Services;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
@ApiStatus.Internal
public interface ClientPlatformHelper {
/**
* Equivalent to {@link ModelManager#getModel(ModelResourceLocation)} but for arbitrary {@link ResourceLocation}s.
*
* @param manager The model manager.
* @param location The model location.
* @return The baked model.
*/
BakedModel getModel(ModelManager manager, ResourceLocation location);
static ClientPlatformHelper get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;
}
final class Instance {
static final @Nullable ClientPlatformHelper INSTANCE;
static final @Nullable Throwable ERROR;
static {
var helper = Services.tryLoad(ClientPlatformHelper.class);
INSTANCE = helper.instance();
ERROR = helper.error();
}
private Instance() {
}
}
}

View File

@@ -12,7 +12,6 @@ import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.Services;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
@@ -21,30 +20,25 @@ import javax.annotation.Nullable;
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/
@ApiStatus.Internal
public interface ComputerCraftAPIClientService
{
static ComputerCraftAPIClientService get()
{
ComputerCraftAPIClientService instance = Instance.INSTANCE;
return instance == null ? Services.raise( ComputerCraftAPIClientService.class, Instance.ERROR ) : instance;
public interface ComputerCraftAPIClientService {
static ComputerCraftAPIClientService get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(ComputerCraftAPIClientService.class, Instance.ERROR) : instance;
}
<T extends ITurtleUpgrade> void registerTurtleUpgradeModeller( @Nonnull TurtleUpgradeSerialiser<T> serialiser, @Nonnull TurtleUpgradeModeller<T> modeller );
<T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
final class Instance
{
final class Instance {
static final @Nullable ComputerCraftAPIClientService INSTANCE;
static final @Nullable Throwable ERROR;
static
{
Services.LoadedService<ComputerCraftAPIClientService> helper = Services.tryLoad( ComputerCraftAPIClientService.class );
static {
var helper = Services.tryLoad(ComputerCraftAPIClientService.class);
INSTANCE = helper.instance();
ERROR = helper.error();
}
private Instance()
{
private Instance() {
}
}
}

View File

@@ -0,0 +1,203 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.core.BlockPos;
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;
/**
* The static entry point to the ComputerCraft API.
* <p>
* Members in this class must be called after ComputerCraft has been initialised, but may be called before it is
* fully loaded.
*/
public final class ComputerCraftAPI {
public static final String MOD_ID = "computercraft";
public static String getInstalledVersion() {
return getInstance().getInstalledVersion();
}
/**
* Creates a numbered directory in a subfolder of the save directory for a given world, and returns that number.
* <p>
* Use in conjunction with createSaveDirMount() to create a unique place for your peripherals or media items to store files.
*
* @param server The server for which the save dir should be created.
* @param parentSubPath The folder path within the save directory where the new directory should be created. eg: "computercraft/disk"
* @return The numerical value of the name of the new folder, or -1 if the folder could not be created for some reason.
* <p>
* eg: if createUniqueNumberedSaveDir( world, "computer/disk" ) was called returns 42, then "computer/disk/42" is now
* available for writing.
* @see #createSaveDirMount(MinecraftServer, String, long)
*/
public static int createUniqueNumberedSaveDir(MinecraftServer server, String parentSubPath) {
return getInstance().createUniqueNumberedSaveDir(server, parentSubPath);
}
/**
* Creates a file system mount that maps to a subfolder of the save directory for a given world, and returns it.
* <p>
* Use in conjunction with Use {@link IComputerAccess#mount(String, Mount)} or {@link IComputerAccess#mountWritable(String, WritableMount)}
* to mount this on a computer's file system.
* <p>
* If the same folder may be mounted on multiple computers at once (for instance, if you provide a network file share),
* the same mount instance should be used for all computers. You should NOT have multiple mount instances for the
* same folder.
*
* @param server The server which the save dir can be found.
* @param subPath The folder path within the save directory that the mount should map to. eg: "disk/42".
* Use {@link #createUniqueNumberedSaveDir(MinecraftServer, String)} to create a new numbered folder
* to use.
* @param capacity The amount of data that can be stored in the directory before it fills up, in bytes.
* @return The newly created mount.
* @see #createUniqueNumberedSaveDir(MinecraftServer, String)
* @see IComputerAccess#mount(String, Mount)
* @see IComputerAccess#mountWritable(String, WritableMount)
* @see Mount
* @see WritableMount
*/
public static WritableMount createSaveDirMount(MinecraftServer server, String subPath, long capacity) {
return getInstance().createSaveDirMount(server, subPath, capacity);
}
/**
* Creates a file system mount to a resource folder, and returns it.
* <p>
* Use in conjunction with {@link IComputerAccess#mount} or {@link IComputerAccess#mountWritable} to mount a
* resource folder onto a computer's file system.
* <p>
* The files in this mount will be a combination of files in all mod jar, and data packs that contain
* resources with the same domain and path. For instance, ComputerCraft's resources are stored in
* "/data/computercraft/lua/rom". We construct a mount for that with
* {@code createResourceMount("computercraft", "lua/rom")}.
*
* @param server The current Minecraft server, from which to read resources from.
* @param domain The domain under which to look for resources. eg: "mymod".
* @param subPath The subPath under which to look for resources. eg: "lua/myfiles".
* @return The mount, or {@code null} if it could be created for some reason.
* @see IComputerAccess#mount(String, Mount)
* @see IComputerAccess#mountWritable(String, WritableMount)
* @see Mount
*/
@Nullable
public static Mount createResourceMount(MinecraftServer server, String domain, String subPath) {
return getInstance().createResourceMount(server, domain, subPath);
}
/**
* Registers a method source for generic peripherals.
*
* @param source The method source to register.
* @see GenericSource
*/
public static void registerGenericSource(GenericSource source) {
getInstance().registerGenericSource(source);
}
/**
* Registers a bundled redstone provider to provide bundled redstone output for blocks.
*
* @param provider The bundled redstone provider to register.
* @see BundledRedstoneProvider
*/
public static void registerBundledRedstoneProvider(BundledRedstoneProvider provider) {
getInstance().registerBundledRedstoneProvider(provider);
}
/**
* If there is a Computer or Turtle at a certain position in the world, get it's bundled redstone output.
*
* @param world The world this block is in.
* @param pos The position this block is at.
* @param side The side to extract the bundled redstone output from.
* @return If there is a block capable of emitting bundled redstone at the location, it's signal (0-65535) will be returned.
* If there is no block capable of emitting bundled redstone at the location, -1 will be returned.
* @see BundledRedstoneProvider
*/
public static int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side) {
return getInstance().getBundledRedstoneOutput(world, pos, side);
}
/**
* Registers a media provider to provide {@link IMedia} implementations for Items.
*
* @param provider The media provider to register.
* @see MediaProvider
*/
public static void registerMediaProvider(MediaProvider provider) {
getInstance().registerMediaProvider(provider);
}
/**
* Attempt to get the game-wide wireless network.
*
* @param server The current Minecraft server.
* @return The global wireless network, or {@code null} if it could not be fetched.
*/
public static PacketNetwork getWirelessNetwork(MinecraftServer server) {
return getInstance().getWirelessNetwork(server);
}
/**
* Register a custom {@link ILuaAPI}, which may be added onto all computers without requiring a peripheral.
* <p>
* Before implementing this interface, consider alternative methods of providing methods. It is generally preferred
* to use peripherals to provide functionality to users.
*
* @param factory The factory for your API subclass.
* @see ILuaAPIFactory
*/
public static void registerAPIFactory(ILuaAPIFactory factory) {
getInstance().registerAPIFactory(factory);
}
/**
* Construct a new wired node for a given wired element.
*
* @param element The element to construct it for
* @return The element's node
* @see WiredElement#getNode()
*/
public static WiredNode createWiredNodeForElement(WiredElement element) {
return getInstance().createWiredNodeForElement(element);
}
/**
* Register a refuel handler for turtles. This may be used to provide alternative fuel sources, such as consuming RF
* batteries.
*
* @param handler The turtle refuel handler.
* @see TurtleRefuelHandler#refuel(ITurtleAccess, ItemStack, int, int)
*/
public static void registerRefuelHandler(TurtleRefuelHandler handler) {
getInstance().registerRefuelHandler(handler);
}
private static ComputerCraftAPIService getInstance() {
return ComputerCraftAPIService.get();
}
}

View File

@@ -0,0 +1,72 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
/**
* Tags provided by ComputerCraft.
*/
public class ComputerCraftTags {
public static class Items {
public static final TagKey<Item> COMPUTER = make("computer");
public static final TagKey<Item> TURTLE = make("turtle");
public static final TagKey<Item> WIRED_MODEM = make("wired_modem");
public static final TagKey<Item> MONITOR = make("monitor");
/**
* Items which can be {@linkplain Item#use(Level, Player, InteractionHand) used} when calling
* {@code turtle.place()}.
* <p>
* This does not cover items who handle placing inside {@link Item#useOn(UseOnContext)}, as that is always
* called.
*/
public static final TagKey<Item> TURTLE_CAN_PLACE = make("turtle_can_place");
private static TagKey<Item> make(String name) {
return TagKey.create(Registries.ITEM, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
}
}
public static class Blocks {
public static final TagKey<Block> COMPUTER = make("computer");
public static final TagKey<Block> TURTLE = make("turtle");
public static final TagKey<Block> WIRED_MODEM = make("wired_modem");
public static final TagKey<Block> MONITOR = make("monitor");
/**
* Blocks which can be broken by any turtle tool.
*/
public static final TagKey<Block> TURTLE_ALWAYS_BREAKABLE = make("turtle_always_breakable");
/**
* Blocks which can be broken by the default shovel tool.
*/
public static final TagKey<Block> TURTLE_SHOVEL_BREAKABLE = make("turtle_shovel_harvestable");
/**
* Blocks which can be broken with the default sword tool.
*/
public static final TagKey<Block> TURTLE_SWORD_BREAKABLE = make("turtle_sword_harvestable");
/**
* Blocks which can be broken with the default hoe tool.
*/
public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable");
private static TagKey<Block> make(String name) {
return TagKey.create(Registries.BLOCK, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
}
}
}

View File

@@ -8,7 +8,7 @@ package dan200.computercraft.api.detail;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -18,10 +18,9 @@ import java.util.Objects;
*
* @param <T> The type the stack's item must have.
*/
public abstract class BasicItemDetailProvider<T> implements IDetailProvider<ItemStack>
{
public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemStack> {
private final Class<T> itemType;
private final String namespace;
private final @Nullable String namespace;
/**
* Create a new item detail provider. Meta will be inserted into a new sub-map named as per {@code namespace}.
@@ -29,9 +28,8 @@ public abstract class BasicItemDetailProvider<T> implements IDetailProvider<Item
* @param itemType The type the stack's item must have.
* @param namespace The namespace to use for this provider.
*/
public BasicItemDetailProvider( String namespace, @Nonnull Class<T> itemType )
{
Objects.requireNonNull( itemType );
public BasicItemDetailProvider(@Nullable String namespace, Class<T> itemType) {
Objects.requireNonNull(itemType);
this.itemType = itemType;
this.namespace = namespace;
}
@@ -41,9 +39,8 @@ public abstract class BasicItemDetailProvider<T> implements IDetailProvider<Item
*
* @param itemType The type the stack's item must have.
*/
public BasicItemDetailProvider( @Nonnull Class<T> itemType )
{
this( null, itemType );
public BasicItemDetailProvider(Class<T> itemType) {
this(null, itemType);
}
/**
@@ -57,23 +54,22 @@ public abstract class BasicItemDetailProvider<T> implements IDetailProvider<Item
* @param stack The item stack to provide details for.
* @param item The item to provide details for.
*/
public abstract void provideDetails( @Nonnull Map<? super String, Object> data, @Nonnull ItemStack stack,
@Nonnull T item );
public abstract void provideDetails(
Map<? super String, Object> data, ItemStack stack, T item
);
@Override
public void provideDetails( @Nonnull Map<? super String, Object> data, @Nonnull ItemStack stack )
{
Item item = stack.getItem();
if( !itemType.isInstance( item ) ) return;
public void provideDetails(Map<? super String, Object> data, ItemStack stack) {
var item = stack.getItem();
if (!itemType.isInstance(item)) return;
// If `namespace` is specified, insert into a new data map instead of the existing one.
Map<? super String, Object> child = namespace == null ? data : new HashMap<>();
provideDetails( child, stack, itemType.cast( item ) );
provideDetails(child, stack, itemType.cast(item));
if( namespace != null )
{
data.put( namespace, child );
if (namespace != null) {
data.put(namespace, child);
}
}
}

View File

@@ -10,7 +10,6 @@ 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.Nonnull;
import javax.annotation.Nullable;
/**
@@ -22,14 +21,12 @@ import javax.annotation.Nullable;
* @param blockEntity The block entity at this position, if it exists.
*/
public record BlockReference(
@Nonnull Level level,
@Nonnull BlockPos pos,
@Nonnull BlockState state,
Level level,
BlockPos pos,
BlockState state,
@Nullable BlockEntity blockEntity
)
{
public BlockReference( Level level, BlockPos pos )
{
this( level, pos, level.getBlockState( pos ), level.getBlockEntity( pos ) );
) {
public BlockReference(Level level, BlockPos pos) {
this(level, pos, level.getBlockState(pos), level.getBlockEntity(pos));
}
}

View File

@@ -5,7 +5,6 @@
*/
package dan200.computercraft.api.detail;
import javax.annotation.Nonnull;
import java.util.Map;
/**
@@ -18,8 +17,7 @@ import java.util.Map;
* @see DetailRegistry
*/
@FunctionalInterface
public interface IDetailProvider<T>
{
public interface DetailProvider<T> {
/**
* Provide additional details for the given object. This method is called by functions such as
* {@code turtle.getItemDetail()} and {@code turtle.inspect()}. New properties should be added to the given
@@ -31,5 +29,5 @@ public interface IDetailProvider<T>
* @param data The full details to be returned. New properties should be added to this map.
* @param object The object to provide details for.
*/
void provideDetails( @Nonnull Map<? super String, Object> data, @Nonnull T object );
void provideDetails(Map<? super String, Object> data, T object);
}

View File

@@ -20,30 +20,33 @@ import java.util.Map;
* @param <T> The type of object that this registry provides details for.
*/
@ApiStatus.NonExtendable
public interface DetailRegistry<T>
{
public interface DetailRegistry<T> {
/**
* Registers a detail provider.
*
* @param provider The detail provider to register.
* @see IDetailProvider
* @see DetailProvider
*/
void addProvider( IDetailProvider<T> provider );
void addProvider(DetailProvider<T> provider);
/**
* Compute basic details about an object. This is cheaper than computing all details operation, and so is suitable
* for when you need to compute the details for a large number of values.
* <p>
* This method <em>MAY</em> be thread safe: consult the instance's documentation for details.
*
* @param object The object to get details for.
* @return The basic details.
*/
Map<String, Object> getBasicDetails( T object );
Map<String, Object> getBasicDetails(T object);
/**
* Compute all details about an object, using {@link #getBasicDetails(Object)} and any registered providers.
* <p>
* This method is <em>NOT</em> thread safe. It should only be called from the computer thread.
*
* @param object The object to get details for.
* @return The computed details.
*/
Map<String, Object> getDetails( T object );
Map<String, Object> getDetails(T object);
}

View File

@@ -12,16 +12,20 @@ import net.minecraft.world.level.block.Block;
/**
* {@link DetailRegistry}s for built-in Minecraft types.
*/
public class VanillaDetailRegistries
{
public class VanillaDetailRegistries {
/**
* Provides details for {@link ItemStack}s.
* <p>
* This instance's {@link DetailRegistry#getBasicDetails(Object)} is thread safe (assuming the stack is immutable)
* and may be called from the computer thread.
*/
public static final DetailRegistry<ItemStack> ITEM_STACK = ComputerCraftAPIService.get().getItemStackDetailRegistry();
/**
* Provides details for {@link BlockReference}, a reference to a {@link Block} in the world.
* <p>
* This instance's {@link DetailRegistry#getBasicDetails(Object)} is thread safe and may be called from the computer
* thread.
*/
public static final DetailRegistry<BlockReference> BLOCK_IN_WORLD = ComputerCraftAPIService.get().getBlockInWorldDetailRegistry();
}

View File

@@ -5,23 +5,24 @@
*/
package dan200.computercraft.api.media;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Represents an item that can be placed in a disk drive and used by a Computer.
* <p>
* Implement this interface on your {@link Item} class to allow it to be used in the drive. Alternatively, register
* a {@link IMediaProvider}.
* a {@link MediaProvider}.
*/
public interface IMedia
{
public interface IMedia {
/**
* Get a string representing the label of this item. Will be called via {@code disk.getLabel()} in lua.
*
@@ -29,7 +30,7 @@ public interface IMedia
* @return The label. ie: "Dan's Programs".
*/
@Nullable
String getLabel( @Nonnull ItemStack stack );
String getLabel(ItemStack stack);
/**
* Set a string representing the label of this item. Will be called vi {@code disk.setLabel()} in lua.
@@ -38,8 +39,7 @@ public interface IMedia
* @param label The string to set the label to.
* @return true if the label was updated, false if the label may not be modified.
*/
default boolean setLabel( @Nonnull ItemStack stack, @Nullable String label )
{
default boolean setLabel(ItemStack stack, @Nullable String label) {
return false;
}
@@ -51,8 +51,7 @@ public interface IMedia
* @return The name, or null if this item does not represent an item with audio.
*/
@Nullable
default String getAudioTitle( @Nonnull ItemStack stack )
{
default String getAudioTitle(ItemStack stack) {
return null;
}
@@ -63,8 +62,7 @@ public interface IMedia
* @return The name, or null if this item does not represent an item with audio.
*/
@Nullable
default SoundEvent getAudio( @Nonnull ItemStack stack )
{
default SoundEvent getAudio(ItemStack stack) {
return null;
}
@@ -73,17 +71,16 @@ public interface IMedia
* be mounted onto the filesystem of the computer while the media is in the disk drive.
*
* @param stack The {@link ItemStack} to modify.
* @param world The world in which the item and disk drive reside.
* @param level The world in which the item and disk drive reside.
* @return The mount, or null if this item does not represent an item with data. If the mount returned also
* implements {@link dan200.computercraft.api.filesystem.IWritableMount}, it will mounted using mountWritable()
* @see IMount
* @see dan200.computercraft.api.filesystem.IWritableMount
* @see dan200.computercraft.api.ComputerCraftAPI#createSaveDirMount(Level, String, long)
* @see dan200.computercraft.api.ComputerCraftAPI#createResourceMount(String, String)
* implements {@link WritableMount}, it will mounted using mountWritable()
* @see Mount
* @see WritableMount
* @see ComputerCraftAPI#createSaveDirMount(MinecraftServer, String, long)
* @see ComputerCraftAPI#createResourceMount(MinecraftServer, String, String)
*/
@Nullable
default IMount createDataMount( @Nonnull ItemStack stack, @Nonnull Level world )
{
default Mount createDataMount(ItemStack stack, ServerLevel level) {
return null;
}
}

View File

@@ -7,24 +7,22 @@ package dan200.computercraft.api.media;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This interface is used to provide {@link IMedia} implementations for {@link ItemStack}.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(IMediaProvider)
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(MediaProvider)
*/
@FunctionalInterface
public interface IMediaProvider
{
public interface MediaProvider {
/**
* Produce an IMedia implementation from an ItemStack.
*
* @param stack The stack from which to extract the media information.
* @return An {@link IMedia} implementation, or {@code null} if the item is not something you wish to handle
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(IMediaProvider)
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(MediaProvider)
*/
@Nullable
IMedia getMedia( @Nonnull ItemStack stack );
IMedia getMedia(ItemStack stack);
}

View File

@@ -6,7 +6,7 @@
package dan200.computercraft.api.network;
/**
* Represents a packet which may be sent across a {@link IPacketNetwork}.
* Represents a packet which may be sent across a {@link PacketNetwork}.
*
* @param channel The channel to send the packet along. Receiving devices should only process packets from on
* channels they are listening to.
@@ -14,17 +14,16 @@ package dan200.computercraft.api.network;
* @param payload The contents of this packet. This should be a "valid" Lua object, safe for queuing as an
* event or returning from a peripheral call.
* @param sender The object which sent this packet.
* @see IPacketSender
* @see IPacketNetwork#transmitSameDimension(Packet, double)
* @see IPacketNetwork#transmitInterdimensional(Packet)
* @see IPacketReceiver#receiveDifferentDimension(Packet)
* @see IPacketReceiver#receiveSameDimension(Packet, double)
* @see PacketSender
* @see PacketNetwork#transmitSameDimension(Packet, double)
* @see PacketNetwork#transmitInterdimensional(Packet)
* @see PacketReceiver#receiveDifferentDimension(Packet)
* @see PacketReceiver#receiveSameDimension(Packet, double)
*/
public record Packet(
int channel,
int replyChannel,
Object payload,
IPacketSender sender
)
{
PacketSender sender
) {
}

View File

@@ -5,29 +5,27 @@
*/
package dan200.computercraft.api.network;
import javax.annotation.Nonnull;
/**
* A packet network represents a collection of devices which can send and receive packets.
*
* @see Packet
* @see IPacketReceiver
* @see PacketReceiver
*/
public interface IPacketNetwork
{
public interface PacketNetwork {
/**
* Add a receiver to the network.
*
* @param receiver The receiver to register to the network.
*/
void addReceiver( @Nonnull IPacketReceiver receiver );
void addReceiver(PacketReceiver receiver);
/**
* Remove a receiver from the network.
*
* @param receiver The device to remove from the network.
*/
void removeReceiver( @Nonnull IPacketReceiver receiver );
void removeReceiver(PacketReceiver receiver);
/**
* Determine whether this network is wireless.
@@ -43,9 +41,9 @@ public interface IPacketNetwork
* @param packet The packet to send.
* @param range The maximum distance this packet will be sent.
* @see #transmitInterdimensional(Packet)
* @see IPacketReceiver#receiveSameDimension(Packet, double)
* @see PacketReceiver#receiveSameDimension(Packet, double)
*/
void transmitSameDimension( @Nonnull Packet packet, double range );
void transmitSameDimension(Packet packet, double range);
/**
* Submit a packet for transmitting across the network. This will route the packet through the network, sending it
@@ -53,7 +51,7 @@ public interface IPacketNetwork
*
* @param packet The packet to send.
* @see #transmitSameDimension(Packet, double)
* @see IPacketReceiver#receiveDifferentDimension(Packet)
* @see PacketReceiver#receiveDifferentDimension(Packet)
*/
void transmitInterdimensional( @Nonnull Packet packet );
void transmitInterdimensional(Packet packet);
}

View File

@@ -8,19 +8,16 @@ package dan200.computercraft.api.network;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nonnull;
/**
* An object on an {@link IPacketNetwork}, capable of receiving packets.
* An object on an {@link PacketNetwork}, capable of receiving packets.
*/
public interface IPacketReceiver
{
public interface PacketReceiver {
/**
* Get the world in which this packet receiver exists.
*
* @return The receivers's world.
*/
@Nonnull
Level getLevel();
/**
@@ -28,7 +25,6 @@ public interface IPacketReceiver
*
* @return The receiver's position.
*/
@Nonnull
Vec3 getPosition();
/**
@@ -41,7 +37,7 @@ public interface IPacketReceiver
* @return The maximum distance this device can send and receive messages.
* @see #isInterdimensional()
* @see #receiveSameDimension(Packet packet, double)
* @see IPacketNetwork#transmitInterdimensional(Packet)
* @see PacketNetwork#transmitInterdimensional(Packet)
*/
double getRange();
@@ -53,7 +49,7 @@ public interface IPacketReceiver
* @return Whether this receiver receives packets from other dimensions.
* @see #getRange()
* @see #receiveDifferentDimension(Packet)
* @see IPacketNetwork#transmitInterdimensional(Packet)
* @see PacketNetwork#transmitInterdimensional(Packet)
*/
boolean isInterdimensional();
@@ -65,10 +61,10 @@ public interface IPacketReceiver
* @param distance The distance this packet has travelled from the source.
* @see Packet
* @see #getRange()
* @see IPacketNetwork#transmitSameDimension(Packet, double)
* @see IPacketNetwork#transmitInterdimensional(Packet)
* @see PacketNetwork#transmitSameDimension(Packet, double)
* @see PacketNetwork#transmitInterdimensional(Packet)
*/
void receiveSameDimension( @Nonnull Packet packet, double distance );
void receiveSameDimension(Packet packet, double distance);
/**
* Receive a network packet from a different dimension.
@@ -76,9 +72,9 @@ public interface IPacketReceiver
* @param packet The packet to receive. Generally you should check that you are listening on the given channel and,
* if so, queue the appropriate modem event.
* @see Packet
* @see IPacketNetwork#transmitInterdimensional(Packet)
* @see IPacketNetwork#transmitSameDimension(Packet, double)
* @see PacketNetwork#transmitInterdimensional(Packet)
* @see PacketNetwork#transmitSameDimension(Packet, double)
* @see #isInterdimensional()
*/
void receiveDifferentDimension( @Nonnull Packet packet );
void receiveDifferentDimension(Packet packet);
}

View File

@@ -8,19 +8,16 @@ package dan200.computercraft.api.network;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nonnull;
/**
* An object on a {@link IPacketNetwork}, capable of sending packets.
* An object on a {@link PacketNetwork}, capable of sending packets.
*/
public interface IPacketSender
{
public interface PacketSender {
/**
* Get the world in which this packet sender exists.
*
* @return The sender's world.
*/
@Nonnull
Level getLevel();
/**
@@ -28,7 +25,6 @@ public interface IPacketSender
*
* @return The sender's position.
*/
@Nonnull
Vec3 getPosition();
/**
@@ -37,6 +33,5 @@ public interface IPacketSender
*
* @return This device's id.
*/
@Nonnull
String getSenderID();
}

View File

@@ -7,28 +7,25 @@ package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.ComputerCraftAPI;
import javax.annotation.Nonnull;
/**
* An object which may be part of a wired network.
* <p>
* Elements should construct a node using {@link ComputerCraftAPI#createWiredNodeForElement(IWiredElement)}. This acts
* Elements should construct a node using {@link ComputerCraftAPI#createWiredNodeForElement(WiredElement)}. This acts
* 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
* {@link IWiredElement} capability for the appropriate sides.
* {@link WiredElement} capability for the appropriate sides.
*/
public interface IWiredElement extends IWiredSender
{
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.
*
* @param change The change which occurred.
* @see IWiredNetworkChange
* @see WiredNetworkChange
*/
default void networkChanged( @Nonnull IWiredNetworkChange change )
{
default void networkChanged(WiredNetworkChange change) {
}
}

View File

@@ -7,25 +7,23 @@ package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* A wired network is composed of one of more {@link IWiredNode}s, a set of connections between them, and a series
* A wired network is composed of one of more {@link WiredNode}s, a set of connections between them, and a series
* of peripherals.
* <p>
* Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if
* there is some path between two nodes then they must be on the same network. {@link IWiredNetwork} will automatically
* there is some path between two nodes then they must be on the same network. {@link WiredNetwork} will automatically
* handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections
* change.
* <p>
* This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently,
* it is generally preferred to use the methods provided by {@link IWiredNode}.
* it is generally preferred to use the methods provided by {@link WiredNode}.
*
* @see IWiredNode#getNetwork()
* @see WiredNode#getNetwork()
*/
public interface IWiredNetwork
{
public interface WiredNetwork {
/**
* Create a connection between two nodes.
* <p>
@@ -36,10 +34,10 @@ public interface IWiredNetwork
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @throws IllegalStateException If neither node is on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see IWiredNode#connectTo(IWiredNode)
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
* @see WiredNode#connectTo(WiredNode)
* @see WiredNetwork#connect(WiredNode, WiredNode)
*/
boolean connect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
boolean connect(WiredNode left, WiredNode right);
/**
* Destroy a connection between this node and another.
@@ -51,10 +49,10 @@ public interface IWiredNetwork
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If either node is not on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see IWiredNode#disconnectFrom(IWiredNode)
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
* @see WiredNode#disconnectFrom(WiredNode)
* @see WiredNetwork#connect(WiredNode, WiredNode)
*/
boolean disconnect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
boolean disconnect(WiredNode left, WiredNode right);
/**
* Sever all connections this node has, removing it from this network.
@@ -66,9 +64,9 @@ public interface IWiredNetwork
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredNode#remove()
* @see WiredNode#remove()
*/
boolean remove( @Nonnull IWiredNode node );
boolean remove(WiredNode node);
/**
* Update the peripherals a node provides.
@@ -79,7 +77,7 @@ public interface IWiredNetwork
* @param node The node to attach peripherals for.
* @param peripherals The new peripherals for this node.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredNode#updatePeripherals(Map)
* @see WiredNode#updatePeripherals(Map)
*/
void updatePeripherals( @Nonnull IWiredNode node, @Nonnull Map<String, IPeripheral> peripherals );
void updatePeripherals(WiredNode node, Map<String, IPeripheral> peripherals);
}

View File

@@ -7,23 +7,20 @@ package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* Represents a change to the objects on a wired network.
*
* @see IWiredElement#networkChanged(IWiredNetworkChange)
* @see WiredElement#networkChanged(WiredNetworkChange)
*/
public interface IWiredNetworkChange
{
public interface WiredNetworkChange {
/**
* A set of peripherals which have been removed. Note that there may be entries with the same name
* in the added and removed set, but with a different peripheral.
*
* @return The set of removed peripherals.
*/
@Nonnull
Map<String, IPeripheral> peripheralsRemoved();
/**
@@ -32,6 +29,5 @@ public interface IWiredNetworkChange
*
* @return The set of added peripherals.
*/
@Nonnull
Map<String, IPeripheral> peripheralsAdded();
}

View File

@@ -5,14 +5,13 @@
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* Wired nodes act as a layer between {@link IWiredElement}s and {@link IWiredNetwork}s.
* Wired nodes act as a layer between {@link WiredElement}s and {@link WiredNetwork}s.
* <p>
* Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These
* methods may be safely used on any thread.
@@ -24,15 +23,13 @@ import java.util.Map;
* Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever
* be used on the main server thread.
*/
public interface IWiredNode extends IPacketNetwork
{
public interface WiredNode extends PacketNetwork {
/**
* The associated element for this network node.
*
* @return This node's element.
*/
@Nonnull
IWiredElement getElement();
WiredElement getElement();
/**
* The network this node is currently connected to. Note that this may change
@@ -42,8 +39,7 @@ public interface IWiredNode extends IPacketNetwork
*
* @return This node's network.
*/
@Nonnull
IWiredNetwork getNetwork();
WiredNetwork getNetwork();
/**
* Create a connection from this node to another.
@@ -52,12 +48,11 @@ public interface IWiredNode extends IPacketNetwork
*
* @param node The other node to connect to.
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
* @see IWiredNode#disconnectFrom(IWiredNode)
* @see WiredNetwork#connect(WiredNode, WiredNode)
* @see WiredNode#disconnectFrom(WiredNode)
*/
default boolean connectTo( @Nonnull IWiredNode node )
{
return getNetwork().connect( this, node );
default boolean connectTo(WiredNode node) {
return getNetwork().connect(this, node);
}
/**
@@ -68,12 +63,11 @@ public interface IWiredNode extends IPacketNetwork
* @param node The other node to disconnect from.
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If {@code node} is not on the same network.
* @see IWiredNetwork#disconnect(IWiredNode, IWiredNode)
* @see IWiredNode#connectTo(IWiredNode)
* @see WiredNetwork#disconnect(WiredNode, WiredNode)
* @see WiredNode#connectTo(WiredNode)
*/
default boolean disconnectFrom( @Nonnull IWiredNode node )
{
return getNetwork().disconnect( this, node );
default boolean disconnectFrom(WiredNode node) {
return getNetwork().disconnect(this, node);
}
/**
@@ -85,11 +79,10 @@ public interface IWiredNode extends IPacketNetwork
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredNetwork#remove(IWiredNode)
* @see WiredNetwork#remove(WiredNode)
*/
default boolean remove()
{
return getNetwork().remove( this );
default boolean remove() {
return getNetwork().remove(this);
}
/**
@@ -99,10 +92,9 @@ public interface IWiredNode extends IPacketNetwork
* that your network element owns.
*
* @param peripherals The new peripherals for this node.
* @see IWiredNetwork#updatePeripherals(IWiredNode, Map)
* @see WiredNetwork#updatePeripherals(WiredNode, Map)
*/
default void updatePeripherals( @Nonnull Map<String, IPeripheral> peripherals )
{
getNetwork().updatePeripherals( this, peripherals );
default void updatePeripherals(Map<String, IPeripheral> peripherals) {
getNetwork().updatePeripherals(this, peripherals);
}
}

View File

@@ -5,18 +5,16 @@
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.IPacketSender;
import dan200.computercraft.api.network.PacketSender;
import javax.annotation.Nonnull;
/**
* An object on a {@link IWiredNetwork} capable of sending packets.
* An object on a {@link WiredNetwork} capable of sending packets.
* <p>
* Unlike a regular {@link IPacketSender}, this must be associated with the node you are attempting to
* Unlike a regular {@link PacketSender}, this must be associated with the node you are attempting to
* to send the packet from.
*/
public interface IWiredSender extends IPacketSender
{
public interface WiredSender extends PacketSender {
/**
* The node in the network representing this object.
* <p>
@@ -25,6 +23,5 @@ public interface IWiredSender extends IPacketSender
*
* @return The node for this element.
*/
@Nonnull
IWiredNode getNode();
WiredNode getNode();
}

View File

@@ -5,53 +5,43 @@
*/
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.IUpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nonnull;
/**
* A base class for {@link IPocketUpgrade}s.
* <p>
* One does not have to use this, but it does provide a convenient template.
*/
public abstract class AbstractPocketUpgrade implements IPocketUpgrade
{
public abstract class AbstractPocketUpgrade implements IPocketUpgrade {
private final ResourceLocation id;
private final String adjective;
private final ItemStack stack;
protected AbstractPocketUpgrade( ResourceLocation id, String adjective, ItemStack stack )
{
protected AbstractPocketUpgrade(ResourceLocation id, String adjective, ItemStack stack) {
this.id = id;
this.adjective = adjective;
this.stack = stack;
}
protected AbstractPocketUpgrade( ResourceLocation id, ItemStack stack )
{
this( id, IUpgradeBase.getDefaultAdjective( id ), stack );
protected AbstractPocketUpgrade(ResourceLocation id, ItemStack stack) {
this(id, UpgradeBase.getDefaultAdjective(id), stack);
}
@Nonnull
@Override
public final ResourceLocation getUpgradeID()
{
public final ResourceLocation getUpgradeID() {
return id;
}
@Nonnull
@Override
public final String getUnlocalisedAdjective()
{
public final String getUnlocalisedAdjective() {
return adjective;
}
@Nonnull
@Override
public final ItemStack getCraftingItem()
{
public final ItemStack getCraftingItem() {
return stack;
}
}

View File

@@ -10,15 +10,13 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
/**
* Wrapper class for pocket computers.
*/
public interface IPocketAccess
{
public interface IPocketAccess {
/**
* Gets the entity holding this item.
* <p>
@@ -45,7 +43,7 @@ public interface IPocketAccess
* {@code 0x000000} and {@code 0xFFFFFF} or -1 to reset to the default colour.
* @see #getColour()
*/
void setColour( int colour );
void setColour(int colour);
/**
* Get the colour of this pocket computer's light as a RGB number.
@@ -63,7 +61,7 @@ public interface IPocketAccess
* {@code 0x000000} and {@code 0xFFFFFF} or -1 to reset to the default colour.
* @see #getLight()
*/
void setLight( int colour );
void setLight(int colour);
/**
* Get the upgrade-specific NBT.
@@ -73,7 +71,6 @@ public interface IPocketAccess
* @return The upgrade's NBT.
* @see #updateUpgradeNBTData()
*/
@Nonnull
CompoundTag getUpgradeNBTData();
/**
@@ -93,6 +90,5 @@ public interface IPocketAccess
*
* @return A collection of all upgrade names.
*/
@Nonnull
Map<ResourceLocation, IPeripheral> getUpgrades();
}

View File

@@ -6,10 +6,9 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.IUpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.world.level.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
@@ -24,8 +23,7 @@ import javax.annotation.Nullable;
*
* @see PocketUpgradeSerialiser For how to register a pocket computer upgrade.
*/
public interface IPocketUpgrade extends IUpgradeBase
{
public interface IPocketUpgrade extends UpgradeBase {
/**
* Creates a peripheral for the pocket computer.
* <p>
@@ -38,7 +36,7 @@ public interface IPocketUpgrade extends IUpgradeBase
* @see #update(IPocketAccess, IPeripheral)
*/
@Nullable
IPeripheral createPeripheral( @Nonnull IPocketAccess access );
IPeripheral createPeripheral(IPocketAccess access);
/**
* Called when the pocket computer item stack updates.
@@ -47,8 +45,7 @@ public interface IPocketUpgrade extends IUpgradeBase
* @param peripheral The peripheral for this upgrade.
* @see #createPeripheral(IPocketAccess)
*/
default void update( @Nonnull IPocketAccess access, @Nullable IPeripheral peripheral )
{
default void update(IPocketAccess access, @Nullable IPeripheral peripheral) {
}
/**
@@ -62,8 +59,7 @@ public interface IPocketUpgrade extends IUpgradeBase
* access the GUI.
* @see #createPeripheral(IPocketAccess)
*/
default boolean onRightClick( @Nonnull Level world, @Nonnull IPocketAccess access, @Nullable IPeripheral peripheral )
{
default boolean onRightClick(Level world, IPocketAccess access, @Nullable IPeripheral peripheral) {
return false;
}
}

View File

@@ -7,24 +7,21 @@ package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import net.minecraft.data.DataGenerator;
import net.minecraftforge.data.event.GatherDataEvent;
import net.minecraft.data.PackOutput;
import javax.annotation.Nonnull;
import java.util.function.Consumer;
/**
* A data provider to generate pocket computer upgrades.
* <p>
* This should be subclassed and registered to a {@link DataGenerator}. Override the {@link #addUpgrades(Consumer)} function,
* construct each upgrade, and pass them off to the provided consumer to generate them.
* This should be subclassed and registered to a {@link DataGenerator.PackGenerator}. Override the
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
* generate them.
*
* @see GatherDataEvent To register your data provider
* @see PocketUpgradeSerialiser
*/
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>>
{
public PocketUpgradeDataProvider( @Nonnull DataGenerator generator )
{
super( generator, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.REGISTRY_ID );
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>> {
public PocketUpgradeDataProvider(PackOutput output) {
super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.REGISTRY_ID);
}
}

View File

@@ -5,21 +5,17 @@
*/
package dan200.computercraft.api.pocket;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.upgrades.IUpgradeBase;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.internal.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.internal.upgrades.SimpleSerialiser;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.SimpleRecipeSerializer;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.IForgeRegistry;
import net.minecraftforge.registries.RegistryManager;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import javax.annotation.Nonnull;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -33,32 +29,15 @@ import java.util.function.Function;
* @see IPocketUpgrade
* @see PocketUpgradeDataProvider
*/
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T>
{
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
* <p>
* This is largely intended for use with Forge Registry methods/classes, such as {@link DeferredRegister} and
* {@link RegistryManager#getRegistry(ResourceKey)}.
*/
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey( new ResourceLocation( ComputerCraft.MOD_ID, "pocket_upgrade_serialiser" ) );
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
/**
* The associated registry.
*
* @return The registry for pocket upgrade serialisers.
* @see #REGISTRY_ID
* @deprecated Use {@link #REGISTRY_ID} directly.
*/
@Deprecated( forRemoval = true )
static IForgeRegistry<PocketUpgradeSerialiser<?>> registry()
{
return RegistryManager.ACTIVE.getRegistry( REGISTRY_ID );
}
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleRecipeSerializer}, but for
* upgrades.
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.
* <p>
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
*
@@ -66,40 +45,32 @@ public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends Upgra
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade
*/
@Nonnull
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simple( @Nonnull Function<ResourceLocation, T> factory )
{
final class Impl extends SimpleSerialiser<T> implements PocketUpgradeSerialiser<T>
{
private Impl( Function<ResourceLocation, T> constructor )
{
super( constructor );
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
final class Impl extends SimpleSerialiser<T> implements PocketUpgradeSerialiser<T> {
private Impl(Function<ResourceLocation, T> constructor) {
super(constructor);
}
}
return new Impl( factory );
return new Impl(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link IUpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
@Nonnull
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simpleWithCustomItem( @Nonnull BiFunction<ResourceLocation, ItemStack, T> factory )
{
final class Impl extends SerialiserWithCraftingItem<T> implements PocketUpgradeSerialiser<T>
{
private Impl( BiFunction<ResourceLocation, ItemStack, T> factory )
{
super( factory );
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
final class Impl extends SerialiserWithCraftingItem<T> implements PocketUpgradeSerialiser<T> {
private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
super(factory);
}
}
return new Impl( factory );
return new Impl(factory);
}
}

View File

@@ -5,20 +5,18 @@
*/
package dan200.computercraft.api.redstone;
import dan200.computercraft.api.ComputerCraftAPI;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import javax.annotation.Nonnull;
/**
* This interface is used to provide bundled redstone output for blocks.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerBundledRedstoneProvider(IBundledRedstoneProvider)
* @see ComputerCraftAPI#registerBundledRedstoneProvider(BundledRedstoneProvider)
*/
@FunctionalInterface
public interface IBundledRedstoneProvider
{
public interface BundledRedstoneProvider {
/**
* Produce an bundled redstone output from a block location.
*
@@ -27,7 +25,7 @@ public interface IBundledRedstoneProvider
* @param side The side to extract the bundled redstone output from.
* @return A number in the range 0-65535 to indicate this block is providing output, or -1 if you do not wish to
* handle this block.
* @see dan200.computercraft.api.ComputerCraftAPI#registerBundledRedstoneProvider(IBundledRedstoneProvider)
* @see ComputerCraftAPI#registerBundledRedstoneProvider(BundledRedstoneProvider)
*/
int getBundledRedstoneOutput( @Nonnull Level world, @Nonnull BlockPos pos, @Nonnull Direction side );
int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side);
}

View File

@@ -5,62 +5,50 @@
*/
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.upgrades.IUpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nonnull;
/**
* A base class for {@link ITurtleUpgrade}s.
* <p>
* One does not have to use this, but it does provide a convenient template.
*/
public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade
{
public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade {
private final ResourceLocation id;
private final TurtleUpgradeType type;
private final String adjective;
private final ItemStack stack;
protected AbstractTurtleUpgrade( ResourceLocation id, TurtleUpgradeType type, String adjective, ItemStack stack )
{
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, String adjective, ItemStack stack) {
this.id = id;
this.type = type;
this.adjective = adjective;
this.stack = stack;
}
protected AbstractTurtleUpgrade( ResourceLocation id, TurtleUpgradeType type, ItemStack stack )
{
this( id, type, IUpgradeBase.getDefaultAdjective( id ), stack );
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, ItemStack stack) {
this(id, type, UpgradeBase.getDefaultAdjective(id), stack);
}
@Nonnull
@Override
public final ResourceLocation getUpgradeID()
{
public final ResourceLocation getUpgradeID() {
return id;
}
@Nonnull
@Override
public final String getUnlocalisedAdjective()
{
public final String getUnlocalisedAdjective() {
return adjective;
}
@Nonnull
@Override
public final TurtleUpgradeType getType()
{
public final TurtleUpgradeType getType() {
return type;
}
@Nonnull
@Override
public final ItemStack getCraftingItem()
{
public final ItemStack getCraftingItem() {
return stack;
}
}

View File

@@ -15,9 +15,8 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.Container;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.items.IItemHandlerModifiable;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
@@ -26,14 +25,13 @@ import javax.annotation.Nullable;
* This should not be implemented by your classes. Do not interact with turtles except via this interface and
* {@link ITurtleUpgrade}.
*/
public interface ITurtleAccess
{
@ApiStatus.NonExtendable
public interface ITurtleAccess {
/**
* Returns the world in which the turtle resides.
*
* @return the world in which the turtle resides.
*/
@Nonnull
Level getLevel();
/**
@@ -41,13 +39,12 @@ public interface ITurtleAccess
*
* @return a vector containing the integer co-ordinates at which the turtle resides.
*/
@Nonnull
BlockPos getPosition();
/**
* Determine if this turtle has been removed.
* <p>
* It's possible for a turtle to be removed while a {@link ITurtleCommand} is executed, for instance if interacting
* It's possible for a turtle to be removed while a {@link TurtleCommand} is executed, for instance if interacting
* with a block causes the turtle to be blown up. It's recommended you check the turtle is still present before
* trying to interact with it again.
* <p>
@@ -70,7 +67,7 @@ public interface ITurtleAccess
* was cancelled.
* @throws UnsupportedOperationException When attempting to teleport on the client side.
*/
boolean teleportTo( @Nonnull Level world, @Nonnull BlockPos pos );
boolean teleportTo(Level world, BlockPos pos);
/**
* Returns a vector containing the floating point co-ordinates at which the turtle is rendered.
@@ -80,8 +77,7 @@ public interface ITurtleAccess
* @return A vector containing the floating point co-ordinates at which the turtle resides.
* @see #getVisualYaw(float)
*/
@Nonnull
Vec3 getVisualPosition( float f );
Vec3 getVisualPosition(float f);
/**
* Returns the yaw the turtle is facing when it is rendered.
@@ -90,7 +86,7 @@ public interface ITurtleAccess
* @return The yaw the turtle is facing.
* @see #getVisualPosition(float)
*/
float getVisualYaw( float f );
float getVisualYaw(float f);
/**
* Returns the world direction the turtle is currently facing.
@@ -98,7 +94,6 @@ public interface ITurtleAccess
* @return The world direction the turtle is currently facing.
* @see #setDirection(Direction)
*/
@Nonnull
Direction getDirection();
/**
@@ -108,7 +103,7 @@ public interface ITurtleAccess
* @param dir The new direction to set. This should be on either the x or z axis (so north, south, east or west).
* @see #getDirection()
*/
void setDirection( @Nonnull Direction dir );
void setDirection(Direction dir);
/**
* Get the currently selected slot in the turtle's inventory.
@@ -128,7 +123,7 @@ public interface ITurtleAccess
* @see #getInventory()
* @see #getSelectedSlot()
*/
void setSelectedSlot( int slot );
void setSelectedSlot(int slot);
/**
* Set the colour of the turtle to a RGB number.
@@ -137,7 +132,7 @@ public interface ITurtleAccess
* and {@code 0xFFFFFF} or -1 to reset to the default colour.
* @see #getColour()
*/
void setColour( int colour );
void setColour(int colour);
/**
* Get the colour of this turtle as a RGB number.
@@ -162,25 +157,9 @@ public interface ITurtleAccess
* Note: this inventory should only be accessed and modified on the server thread.
*
* @return This turtle's inventory
* @see #getItemHandler()
*/
@Nonnull
Container getInventory();
/**
* Get the inventory of this turtle as an {@link IItemHandlerModifiable}.
* <p>
* Note: this inventory should only be accessed and modified on the server thread.
*
* @return This turtle's inventory
* @see #getInventory()
* @see IItemHandlerModifiable
* @deprecated Use {@link #getInventory()} directly.
*/
@Nonnull
@Deprecated( forRemoval = true )
IItemHandlerModifiable getItemHandler();
/**
* Determine whether this turtle will require fuel when performing actions.
*
@@ -209,7 +188,7 @@ public interface ITurtleAccess
* @see #addFuel(int)
* @see #consumeFuel(int)
*/
void setFuelLevel( int fuel );
void setFuelLevel(int fuel);
/**
* Get the maximum amount of fuel a turtle can hold.
@@ -226,7 +205,7 @@ public interface ITurtleAccess
* greater than the current fuel level of the turtle. No fuel will be consumed if {@code false} is returned.
* @throws UnsupportedOperationException When attempting to consume fuel on the client side.
*/
boolean consumeFuel( int fuel );
boolean consumeFuel(int fuel);
/**
* Increase the turtle's fuel level by the given amount.
@@ -234,7 +213,7 @@ public interface ITurtleAccess
* @param fuel The amount to refuel with.
* @throws UnsupportedOperationException When attempting to refuel on the client side.
*/
void addFuel( int fuel );
void addFuel(int fuel);
/**
* Adds a custom command to the turtles command queue. Unlike peripheral methods, these custom commands will be executed
@@ -247,11 +226,10 @@ public interface ITurtleAccess
* @return The objects the command returned when executed. you should probably return these to the player
* unchanged if called from a peripheral method.
* @throws UnsupportedOperationException When attempting to execute a command on the client side.
* @see ITurtleCommand
* @see TurtleCommand
* @see MethodResult#pullEvent(String, ILuaCallback)
*/
@Nonnull
MethodResult executeCommand( @Nonnull ITurtleCommand command );
MethodResult executeCommand(TurtleCommand command);
/**
* Start playing a specific animation. This will prevent other turtle commands from executing until
@@ -261,7 +239,7 @@ public interface ITurtleAccess
* @throws UnsupportedOperationException When attempting to execute play an animation on the client side.
* @see TurtleAnimation
*/
void playAnimation( @Nonnull TurtleAnimation animation );
void playAnimation(TurtleAnimation animation);
/**
* Returns the turtle on the specified side of the turtle, if there is one.
@@ -271,7 +249,7 @@ public interface ITurtleAccess
* @see #setUpgrade(TurtleSide, ITurtleUpgrade)
*/
@Nullable
ITurtleUpgrade getUpgrade( @Nonnull TurtleSide side );
ITurtleUpgrade getUpgrade(TurtleSide side);
/**
* Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
@@ -280,7 +258,7 @@ public interface ITurtleAccess
* @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgrade(TurtleSide)
*/
void setUpgrade( @Nonnull TurtleSide side, @Nullable ITurtleUpgrade upgrade );
void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade);
/**
* Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
@@ -289,7 +267,7 @@ public interface ITurtleAccess
* @return The peripheral created by the upgrade on the specified side of the turtle, {@code null} if none exists.
*/
@Nullable
IPeripheral getPeripheral( @Nonnull TurtleSide side );
IPeripheral getPeripheral(TurtleSide side);
/**
* Get an upgrade-specific NBT compound, which can be used to store arbitrary data.
@@ -301,8 +279,7 @@ public interface ITurtleAccess
* @return The upgrade-specific data.
* @see #updateUpgradeNBTData(TurtleSide)
*/
@Nonnull
CompoundTag getUpgradeNBTData( @Nullable TurtleSide side );
CompoundTag getUpgradeNBTData(TurtleSide side);
/**
* Mark the upgrade-specific data as dirty on a specific side. This is required for the data to be synced to the
@@ -311,5 +288,5 @@ public interface ITurtleAccess
* @param side The side to mark dirty.
* @see #updateUpgradeNBTData(TurtleSide)
*/
void updateUpgradeNBTData( @Nonnull TurtleSide side );
void updateUpgradeNBTData(TurtleSide side);
}

View File

@@ -6,12 +6,9 @@
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.IUpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.core.Direction;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.level.BlockEvent;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
@@ -27,15 +24,13 @@ import javax.annotation.Nullable;
*
* @see TurtleUpgradeSerialiser For how to register a turtle upgrade.
*/
public interface ITurtleUpgrade extends IUpgradeBase
{
public interface ITurtleUpgrade extends UpgradeBase {
/**
* Return whether this turtle adds a tool or a peripheral to the turtle.
*
* @return The type of upgrade this is.
* @see TurtleUpgradeType for the differences between them.
*/
@Nonnull
TurtleUpgradeType getType();
/**
@@ -51,8 +46,7 @@ public interface ITurtleUpgrade extends IUpgradeBase
* and this method is not expected to be called.
*/
@Nullable
default IPeripheral createPeripheral( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side )
{
default IPeripheral createPeripheral(ITurtleAccess turtle, TurtleSide side) {
return null;
}
@@ -60,8 +54,8 @@ public interface ITurtleUpgrade extends IUpgradeBase
* Will only be called for Tool turtle. Called when turtle.dig() or turtle.attack() is called
* by the turtle, and the tool is required to do some work.
* <p>
* Conforming implementations should fire {@link BlockEvent.BreakEvent} for digging {@link AttackEntityEvent}
* for attacking.
* Conforming implementations should fire loader-specific events when using the tool, for instance Forge's
* {@code AttackEntityEvent}.
*
* @param turtle Access to the turtle that the tool resides on.
* @param side Which side of the turtle (left or right) the tool resides on.
@@ -74,9 +68,7 @@ public interface ITurtleUpgrade extends IUpgradeBase
* a swinging animation. You may return {@code null} if this turtle is a Peripheral and this method is not expected
* to be called.
*/
@Nonnull
default TurtleCommandResult useTool( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side, @Nonnull TurtleVerb verb, @Nonnull Direction direction )
{
default TurtleCommandResult useTool(ITurtleAccess turtle, TurtleSide side, TurtleVerb verb, Direction direction) {
return TurtleCommandResult.failure();
}
@@ -86,7 +78,6 @@ public interface ITurtleUpgrade extends IUpgradeBase
* @param turtle Access to the turtle that the upgrade resides on.
* @param side Which side of the turtle (left or right) the upgrade resides on.
*/
default void update( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side )
{
default void update(ITurtleAccess turtle, TurtleSide side) {
}
}

View File

@@ -12,8 +12,7 @@ package dan200.computercraft.api.turtle;
*
* @see ITurtleAccess#playAnimation(TurtleAnimation)
*/
public enum TurtleAnimation
{
public enum TurtleAnimation {
/**
* An animation which does nothing. This takes no time to complete.
*

View File

@@ -5,16 +5,14 @@
*/
package dan200.computercraft.api.turtle;
import javax.annotation.Nonnull;
/**
* An interface for objects executing custom turtle commands, used with {@link ITurtleAccess#executeCommand(ITurtleCommand)}.
* An interface for objects executing custom turtle commands, used with {@link ITurtleAccess#executeCommand(TurtleCommand)}.
*
* @see ITurtleAccess#executeCommand(ITurtleCommand)
* @see ITurtleAccess#executeCommand(TurtleCommand)
*/
@FunctionalInterface
public interface ITurtleCommand
{
public interface TurtleCommand {
/**
* Will be called by the turtle on the main thread when it is time to execute the custom command.
* <p>
@@ -23,11 +21,10 @@ public interface ITurtleCommand
*
* @param turtle Access to the turtle for whom the command was issued.
* @return A result, indicating whether this action succeeded or not.
* @see ITurtleAccess#executeCommand(ITurtleCommand)
* @see ITurtleAccess#executeCommand(TurtleCommand)
* @see TurtleCommandResult#success()
* @see TurtleCommandResult#failure(String)
* @see TurtleCommandResult
*/
@Nonnull
TurtleCommandResult execute( @Nonnull ITurtleAccess turtle );
TurtleCommandResult execute(ITurtleAccess turtle);
}

View File

@@ -7,28 +7,24 @@ package dan200.computercraft.api.turtle;
import net.minecraft.core.Direction;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Used to indicate the result of executing a turtle command.
*
* @see ITurtleCommand#execute(ITurtleAccess)
* @see TurtleCommand#execute(ITurtleAccess)
* @see ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, Direction)
*/
public final class TurtleCommandResult
{
private static final TurtleCommandResult EMPTY_SUCCESS = new TurtleCommandResult( true, null, null );
private static final TurtleCommandResult EMPTY_FAILURE = new TurtleCommandResult( false, null, null );
public final class TurtleCommandResult {
private static final TurtleCommandResult EMPTY_SUCCESS = new TurtleCommandResult(true, null, null);
private static final TurtleCommandResult EMPTY_FAILURE = new TurtleCommandResult(false, null, null);
/**
* Create a successful command result with no result.
*
* @return A successful command result with no values.
*/
@Nonnull
public static TurtleCommandResult success()
{
public static TurtleCommandResult success() {
return EMPTY_SUCCESS;
}
@@ -38,11 +34,9 @@ public final class TurtleCommandResult
* @param results The results of executing this command.
* @return A successful command result with the given values.
*/
@Nonnull
public static TurtleCommandResult success( @Nullable Object[] results )
{
if( results == null || results.length == 0 ) return EMPTY_SUCCESS;
return new TurtleCommandResult( true, null, results );
public static TurtleCommandResult success(@Nullable Object[] results) {
if (results == null || results.length == 0) return EMPTY_SUCCESS;
return new TurtleCommandResult(true, null, results);
}
/**
@@ -50,9 +44,7 @@ public final class TurtleCommandResult
*
* @return A failed command result with no message.
*/
@Nonnull
public static TurtleCommandResult failure()
{
public static TurtleCommandResult failure() {
return EMPTY_FAILURE;
}
@@ -62,19 +54,16 @@ public final class TurtleCommandResult
* @param errorMessage The error message to provide.
* @return A failed command result with a message.
*/
@Nonnull
public static TurtleCommandResult failure( @Nullable String errorMessage )
{
if( errorMessage == null ) return EMPTY_FAILURE;
return new TurtleCommandResult( false, errorMessage, null );
public static TurtleCommandResult failure(@Nullable String errorMessage) {
if (errorMessage == null) return EMPTY_FAILURE;
return new TurtleCommandResult(false, errorMessage, null);
}
private final boolean success;
private final String errorMessage;
private final Object[] results;
private final @Nullable String errorMessage;
private final @Nullable Object[] results;
private TurtleCommandResult( boolean success, String errorMessage, Object[] results )
{
private TurtleCommandResult(boolean success, @Nullable String errorMessage, @Nullable Object[] results) {
this.success = success;
this.errorMessage = errorMessage;
this.results = results;
@@ -85,8 +74,7 @@ public final class TurtleCommandResult
*
* @return If the command was successful.
*/
public boolean isSuccess()
{
public boolean isSuccess() {
return success;
}
@@ -96,8 +84,7 @@ public final class TurtleCommandResult
* @return The command's error message, or {@code null} if it was a success.
*/
@Nullable
public String getErrorMessage()
{
public String getErrorMessage() {
return errorMessage;
}
@@ -107,8 +94,7 @@ public final class TurtleCommandResult
* @return The command's result, or {@code null} if it was a failure.
*/
@Nullable
public Object[] getResults()
{
public Object[] getResults() {
return results;
}
}

View File

@@ -16,8 +16,7 @@ import java.util.OptionalInt;
*
* @see ComputerCraftAPI#registerRefuelHandler(TurtleRefuelHandler)
*/
public interface TurtleRefuelHandler
{
public interface TurtleRefuelHandler {
/**
* Refuel a turtle using an item.
*
@@ -31,5 +30,5 @@ public interface TurtleRefuelHandler
* {@code OptionalInt#of(0)} if so), but should <em>NOT</em> modify the stack or inventory.
* @return The amount of fuel gained, or {@link OptionalInt#empty()} if this handler does not accept the given item.
*/
OptionalInt refuel( ITurtleAccess turtle, ItemStack stack, int slot, int limit );
OptionalInt refuel(ITurtleAccess turtle, ItemStack stack, int slot, int limit);
}

View File

@@ -8,8 +8,7 @@ package dan200.computercraft.api.turtle;
/**
* An enum representing the two sides of the turtle that a turtle turtle might reside.
*/
public enum TurtleSide
{
public enum TurtleSide {
/**
* The turtle's left side (where the pickaxe usually is on a Wireless Mining Turtle).
*/

View File

@@ -8,34 +8,33 @@ package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.data.event.GatherDataEvent;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.Consumer;
/**
* A data provider to generate turtle upgrades.
* <p>
* This should be subclassed and registered to a {@link DataGenerator}. Override the {@link #addUpgrades(Consumer)} function,
* construct each upgrade, and pass them off to the provided consumer to generate them.
* This should be subclassed and registered to a {@link DataGenerator.PackGenerator}. Override the
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
* generate them.
*
* @see GatherDataEvent To register your data provider
* @see TurtleUpgradeSerialiser
*/
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>>
{
private static final ResourceLocation TOOL_ID = new ResourceLocation( ComputerCraftAPI.MOD_ID, "tool" );
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {
private static final ResourceLocation TOOL_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "tool");
public TurtleUpgradeDataProvider( DataGenerator generator )
{
super( generator, "Turtle Upgrades", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.REGISTRY_ID );
public TurtleUpgradeDataProvider(PackOutput output) {
super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.REGISTRY_ID);
}
/**
@@ -46,10 +45,8 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
* to specify {@link ToolBuilder#damageMultiplier(float)} and {@link ToolBuilder#breakable(TagKey)}.
* @return A tool builder,
*/
@Nonnull
public final ToolBuilder tool( @Nonnull ResourceLocation id, @Nonnull Item item )
{
return new ToolBuilder( id, existingSerialiser( TOOL_ID ), item );
public final ToolBuilder tool(ResourceLocation id, Item item) {
return new ToolBuilder(id, existingSerialiser(TOOL_ID), item);
}
/**
@@ -57,18 +54,16 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
*
* @see #tool(ResourceLocation, Item)
*/
public static class ToolBuilder
{
public static class ToolBuilder {
private final ResourceLocation id;
private final TurtleUpgradeSerialiser<?> serialiser;
private final Item toolItem;
private String adjective;
private Item craftingItem;
private Float damageMultiplier = null;
private TagKey<Block> breakable;
private @Nullable String adjective;
private @Nullable Item craftingItem;
private @Nullable Float damageMultiplier = null;
private @Nullable TagKey<Block> breakable;
ToolBuilder( ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem )
{
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
this.id = id;
this.serialiser = serialiser;
this.toolItem = toolItem;
@@ -81,9 +76,7 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
* @param adjective The new adjective to use.
* @return The tool builder, for further use.
*/
@Nonnull
public ToolBuilder adjective( @Nonnull String adjective )
{
public ToolBuilder adjective(String adjective) {
this.adjective = adjective;
return this;
}
@@ -95,9 +88,7 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
* @param craftingItem The item used to craft this upgrade.
* @return The tool builder, for further use.
*/
@Nonnull
public ToolBuilder craftingItem( @Nonnull Item craftingItem )
{
public ToolBuilder craftingItem(Item craftingItem) {
this.craftingItem = craftingItem;
return this;
}
@@ -107,10 +98,9 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
* get the final damage.
*
* @param damageMultiplier The damage multiplier.
* @return The tool builder, for futher use.
* @return The tool builder, for further use.
*/
public ToolBuilder damageMultiplier( float damageMultiplier )
{
public ToolBuilder damageMultiplier(float damageMultiplier) {
this.damageMultiplier = damageMultiplier;
return this;
}
@@ -124,8 +114,7 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
* @return The tool builder, for further use.
* @see ComputerCraftTags.Blocks
*/
public ToolBuilder breakable( @Nonnull TagKey<Block> breakable )
{
public ToolBuilder breakable(TagKey<Block> breakable) {
this.breakable = breakable;
return this;
}
@@ -135,18 +124,16 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
*
* @param add The callback given to {@link #addUpgrades(Consumer)}.
*/
public void add( @Nonnull Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add )
{
add.accept( new Upgrade<>( id, serialiser, s -> {
s.addProperty( "item", ForgeRegistries.ITEMS.getKey( toolItem ).toString() );
if( adjective != null ) s.addProperty( "adjective", adjective );
if( craftingItem != null )
{
s.addProperty( "craftItem", ForgeRegistries.ITEMS.getKey( craftingItem ).toString() );
public void add(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add) {
add.accept(new Upgrade<>(id, serialiser, s -> {
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, toolItem).toString());
if (adjective != null) s.addProperty("adjective", adjective);
if (craftingItem != null) {
s.addProperty("craftItem", PlatformHelper.get().getRegistryKey(Registries.ITEM, craftingItem).toString());
}
if( damageMultiplier != null ) s.addProperty( "damageMultiplier", damageMultiplier );
if( breakable != null ) s.addProperty( "breakable", breakable.location().toString() );
} ) );
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
if (breakable != null) s.addProperty("breakable", breakable.location().toString());
}));
}
}
}

View File

@@ -5,37 +5,30 @@
*/
package dan200.computercraft.api.turtle;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.client.ComputerCraftAPIClient;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.upgrades.IUpgradeBase;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.internal.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.internal.upgrades.SimpleSerialiser;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleRecipeSerializer;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.IForgeRegistry;
import net.minecraftforge.registries.RegistryManager;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import javax.annotation.Nonnull;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Reads a {@link ITurtleUpgrade} from disk and reads/writes it to a network packet.
* <p>
* These should be registered in a {@link IForgeRegistry} while the game is loading, much like {@link RecipeSerializer}s.
* It is suggested you use a {@link DeferredRegister}.
* These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
* <p>
* If your turtle upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
* {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
*
* <h2>Example</h2>
* <h2>Example (Forge)</h2>
* <pre>{@code
* static final DeferredRegister<TurtleUpgradeSerialiser<?>> SERIALISERS = DeferredRegister.create( TurtleUpgradeSerialiser.TYPE, "my_mod" );
*
@@ -57,7 +50,7 @@ import java.util.function.Function;
* }</pre>
* <p>
* Finally, we need to register a model for our upgrade. This is done with
* {@link ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller)}:
* {@link dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller}:
*
* <pre>{@code
* // Register our model inside FMLClientSetupEvent
@@ -69,34 +62,17 @@ import java.util.function.Function;
* @param <T> The type of turtle upgrade this is responsible for serialising.
* @see ITurtleUpgrade
* @see TurtleUpgradeDataProvider
* @see TurtleUpgradeModeller
* @see dan200.computercraft.api.client.turtle.TurtleUpgradeModeller
*/
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T>
{
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
* <p>
* This is largely intended for use with Forge Registry methods/classes, such as {@link DeferredRegister} and
* {@link RegistryManager#getRegistry(ResourceKey)}.
*/
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey( new ResourceLocation( ComputerCraft.MOD_ID, "turtle_upgrade_serialiser" ) );
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
/**
* The associated registry.
*
* @return The registry for pocket upgrade serialisers.
* @see #REGISTRY_ID
* @deprecated Use {@link #REGISTRY_ID} directly.
*/
@Deprecated( forRemoval = true )
static IForgeRegistry<TurtleUpgradeSerialiser<?>> registry()
{
return RegistryManager.ACTIVE.getRegistry( REGISTRY_ID );
}
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleRecipeSerializer}, but for
* upgrades.
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.
* <p>
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
*
@@ -104,40 +80,32 @@ public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends Upgra
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade
*/
@Nonnull
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simple( @Nonnull Function<ResourceLocation, T> factory )
{
final class Impl extends SimpleSerialiser<T> implements TurtleUpgradeSerialiser<T>
{
private Impl( Function<ResourceLocation, T> constructor )
{
super( constructor );
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
final class Impl extends SimpleSerialiser<T> implements TurtleUpgradeSerialiser<T> {
private Impl(Function<ResourceLocation, T> constructor) {
super(constructor);
}
}
return new Impl( factory );
return new Impl(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link IUpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
@Nonnull
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simpleWithCustomItem( @Nonnull BiFunction<ResourceLocation, ItemStack, T> factory )
{
final class Impl extends SerialiserWithCraftingItem<T> implements TurtleUpgradeSerialiser<T>
{
private Impl( BiFunction<ResourceLocation, ItemStack, T> factory )
{
super( factory );
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
final class Impl extends SerialiserWithCraftingItem<T> implements TurtleUpgradeSerialiser<T> {
private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
super(factory);
}
}
return new Impl( factory );
return new Impl(factory);
}
}

View File

@@ -10,8 +10,7 @@ package dan200.computercraft.api.turtle;
*
* @see ITurtleUpgrade#getType()
*/
public enum TurtleUpgradeType
{
public enum TurtleUpgradeType {
/**
* A tool is rendered as an item on the side of the turtle, and responds to the {@code turtle.dig()}
* and {@code turtle.attack()} methods (Such as pickaxe or sword on Mining and Melee turtles).
@@ -31,13 +30,11 @@ public enum TurtleUpgradeType
*/
BOTH;
public boolean isTool()
{
public boolean isTool() {
return this == TOOL || this == BOTH;
}
public boolean isPeripheral()
{
public boolean isPeripheral() {
return this == PERIPHERAL || this == BOTH;
}
}

View File

@@ -14,8 +14,7 @@ import net.minecraft.core.Direction;
* @see ITurtleUpgrade#getType()
* @see ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, Direction)
*/
public enum TurtleVerb
{
public enum TurtleVerb {
/**
* The turtle called {@code turtle.dig()}, {@code turtle.digUp()} or {@code turtle.digDown()}.
*/

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