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

Compare commits

...

102 Commits

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

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

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

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

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

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

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

Fixes #2059
2025-01-12 10:37:27 +00:00
Jonathan Coates
3c46b8acd7 Clean up Javadocs a little
I've no motivation for modding right now, but always got time for build
system busywork!

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

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

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

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

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

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

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

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

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

Data generators are no longer bundled with the jar, which does reduce
file size, but by a tiny amount (~70KiB).
2024-12-05 12:03:45 +00:00
Jonathan Coates
7c02979c22 Upload jars in CI earlier
This means they're uploaded even if the linters or tests fail.
2024-11-30 12:07:55 +00:00
Jonathan Coates
fdb65c9368 Link to monitor events from the main monitor page 2024-11-28 10:31:36 +00:00
Jonathan Coates
ea670cc358 Try to unify our config files a bit
I've tried so many rewrites of the config system over the last few
months, in an attempt to get started on #1727. All of them stink, so
this is an attempt to apply some of the cleanup.

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

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

 - Remove our separate Trie structure, and just encode the tree as part
   of the children of a Group.
2024-11-24 21:53:04 +00:00
Jonathan Coates
1963e0160f Bump CC:T to 1.114.2
Ahhhh, I hate graphics code.
2024-11-23 09:34:03 +00:00
Jonathan Coates
9a06904634 Only skip cursor if it exists
If the cursor is not visible then we'd end up blinking the last
character on the screen. And if the screen was empty we'd spew the logs
with GL errors.
2024-11-23 09:31:04 +00:00
Jonathan Coates
5e24ad17d7 Bump CC:T to 1.114.1 2024-11-23 09:14:53 +00:00
Jonathan Coates
8b1cb09ddf Update translations 2024-11-23 09:10:50 +00:00
Jonathan Coates
d1a6b043c2 Clean up monitor cursor rendering
- Use the correct index count for the cursor quad. Monitors are now
   rendered as quads, rather than triangles.
 - *Skip* rendering the cursor vertex, rather than additionally
   rendering it.

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

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

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

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

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

 - Add the new redstone relay block.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 - Fix rotation of turtle item models.

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

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

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

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

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

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

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

Fixes #1955.
2024-08-25 09:24:10 +01:00
Jonathan Coates
c36c8605bf Mention Crowdin in our documentation 2024-08-24 22:55:14 +01:00
Jonathan Coates
3c72a00d46 Write translations to the root 2024-08-24 18:46:22 +01:00
Jonathan Coates
58aefc8df8 Add Crowdin config
We may end up reverting this, but let's give it a go at least.
2024-08-24 18:29:17 +01:00
Konuma Takaki
97ddfc2794 Japanese translation update (#1950) 2024-08-22 09:43:55 +00:00
Jonathan Coates
6e4ec86586 Fix repeated words in DFPWM docs
Even more of these! I really need to stop getting bored half way through
writing sentences and then continue writing at the wrong place. Or,
y'know, get better at proof reading.
2024-08-20 09:22:04 +01:00
Jonathan Coates
d24984c1d5 Bump CC:T to 1.113.0 2024-08-19 18:28:22 +01:00
Jonathan Coates
8080dcdd9e Fix pocket computers not being active in the off-hand
While Item.inventoryTick is passed a slot number, apparently that slot
corresponds to the offset within a particular inventory compartment
(such as the main inventory or armour)[^1], rather than the inventory as
a whole.

In the case of the off-hand, this means the pocket computer is set to be
in slot 0. When we next tick the computer (to send terminal updates), we
then assume the item has gone missing, and so skip sending updates.

Fixes #1945.

[^1]: A fun side effect of this is that the "selected" flag is true for
  the off-hand iff the player has slot 0 active. This whole thing feels
  like a vanilla bug, but who knows!
2024-08-19 17:34:39 +01:00
Jonathan Coates
d7cea55e2a Add recipes for pocket computers too
This is a little daft (recipes feel a little clumsy and tacked on), but
it's better than them being nowhere.
2024-08-19 08:10:50 +01:00
Jonathan Coates
9b2f974a81 Some tweaks to the turtle docs
- Mention only diamond tools can be used as upgrades, and be clearer
   that only the pickaxe and sword are actually useful. We probably
   could be more explicit here, but struggled to find a way to do that.

 - Expliitly list which peripherals can be equipped.

 - Add turtle recipes.
2024-08-19 08:06:00 +01:00
Jonathan Coates
43770fa9bd Remove usage of deprecated legacy Java Date API
I've been staring at this warning for years, and ignored it thinking it
would be a pain to fix. I'm a fool!
2024-08-18 12:56:36 +01:00
Jonathan Coates
80c7a54ad4 Test path manipulation methods sanitise correctly
There's some nuance here with pattern vs non-pattern characters, so
useful to test for that.
2024-08-18 12:49:33 +01:00
Jonathan Coates
e57b6fede2 Test behaviour of fs.getName/getDir with relative paths
It's not entirely clear what the correct behaviour of fs.getDir("..")
should be, and there's not much consensus between various languages.

I think the intended behaviour of this function is to move "up" one
directory level in the path, meaning it should return "../..".
2024-08-18 11:38:10 +01:00
Jonathan Coates
34a2fd039f Clarify behaviour of readAll at the end of a file
This should return an empty string, to match PUC Lua.
2024-08-18 11:03:17 +01:00
Jonathan Coates
3299d0e72a Search for items in the whole gametest structure
This fixes some flakiness where items get thrown outside a 1 block
radius.
2024-08-18 10:53:40 +01:00
Jonathan Coates
b89e2615db Don't add lore to item details when empty 2024-08-18 10:28:16 +01:00
Jonathan Coates
cdcd82679c Don't share singleton collections
CC tries to preserve sharing of objects when crossing the Lua/Java
boundary. For instance, if you queue (or send over a modem)
`{ tbl, tbl }`, then the returned table will have `x[1] == x[2]`.

However, this sharing causes issues with Java singletons. If some code
uses a singleton collection (such as List.of()) in multiple places, then
the same Lua table will be used in all those locations. It's incredibly
easy to accidentally, especially when using using Stream.toList.

For now, we special case these collections and don't de-duplicate them.
I'm not wild about this (it's a bit of a hack!), but I think it's
probably the easiest solution for now.

Fixes #1940
2024-08-18 10:20:54 +01:00
Jonathan Coates
cdfa866760 Fix several repeated words
Depressing how many of these there are. Some come from Dan though
(including one in the LICENSE!), so at least it's not just me!
2024-08-17 11:39:14 +01:00
Jonathan Coates
aa8078ddeb Allow placing printouts in lecterns
- Add a new custom lectern block, that is used to hold the printed
   pages. We have to roll quite a lot of custom logic, so this is much
   cleaner than trying to mixin to the existing lectern code.

 - Add a new (entity) model for printed pages and books placed on a
   lectern. I did originally think about just rendering the item (or the
   in-hand/map version), but I think this is a bit more consistent with
   vanilla.

   However, we do still need to sync the item to the client (mostly to
   get the current page count!). There is a risk of chunkbanning here,
   but I think it's much harder than vanilla, due to the significantly
   reduced page limit.
2024-08-15 21:19:13 +01:00
Jonathan Coates
7e53c19d74 Add a specialised menu for printouts
Rather than having a general "held-item" container, we now have a
specialised one for printouts. This now is a little more general,
supporting any container (not just the player inventory), and syncs the
current page via a data slot.

Currently this isn't especially useful, but should make it a little
easier to add lectern support in the future.
2024-08-15 20:58:09 +01:00
Jonathan Coates
b7a8432cfb Fix turtles capturing their own drops when broken
There's a whole load of gnarly issues that occur when a turtle is broken
mid-dig/attack (normally due to an explosion). We fixed most of these in
24af36743d, but not perfectly.

Part of the fix here was to not capture drops if the turtle BE has been
removed. However, on removal, turtles drop their items *before* removing
the BE. This meant that the drop consumer still triggered, and attempted
to insert items back into the turtle.

This bug only triggers if the turtle contains a stack larger than 10
(ish, I think) items, which is possibly why I'd never reproduced before.

We now drop items after removing the BE, which resolves the issue.

Fixes #1936.
2024-08-15 10:32:54 +01:00
Jonathan Coates
356c8e8aeb Fix disk drives not setting/clearing removed flag
This was originally noticed on 1.21, as it causes disk drives to not be
detected as peripherals. However, things will still be broken (albeit
more subtly) on 1.20, so worth fixing here.
2024-08-15 09:03:33 +01:00
Jonathan Coates
ed283155f7 Update to Gradle 8.10 2024-08-15 08:49:46 +01:00
Jonathan Coates
87dfad026e Add a test for exploding turtles
There's been a couple of bug reports in the past where the game would
crash if a turtle is destroyed while breaking a block (typically due to
the block exploding). This commit adds a test, to ensure that this is
handled gracefully.

I'm not entirely sure this is testing the right thing. Looking at the
issues in question, it doesn't look like I ever managed to reproduce the
bug. However, it's hopefully at least a quick sanity test to check we
never break this case.
2024-08-14 22:41:31 +01:00
Jonathan Coates
bb97c465d9 Fix computers/turtles not being dropped on explosion
Computer drops are currently[^1] implemented via a dynamic drop. To
support this, we need to inject the dynamic drop into the loot
parameters.

We currently do this by implementing our own drop logic in
playerWillDestroy[^2], manually creating the loot params and adding our
additional drop. However, if the item is dropped via some other method
(such as via explosions), we'll go through vanilla's drop logic and so
never add the dynamic drop!

The correct way to do this is to override getDrops to add the dynamic
drop instead. I don't know why we didn't always do this -- the code in
question was first written for MC 1.14[^3], when things were very
different.

[^1]: This is no longer the case on 1.21, where we can just copy
      capabilities.

[^2]: We need to override vanilla's drop behaviour to ensure items are
      dropped in creative mode.

[^3]: See 594bc4203c. Which probably means
      the bug has been around for 5 years :/.
2024-08-14 21:12:30 +01:00
Jonathan Coates
9484315d37 Fix return type of Vector.dot
Closes #1932
2024-08-11 14:11:14 +01:00
Jonathan Coates
be59f1a875 Clarify some quicks of JSON serialisation
There's a mismatch between how Lua and JSON's values are defined, which
means that serialisation is a little confusing at times. This commit
attempts to document them a little better.

Closes #1885, closes #1920
2024-08-11 12:25:28 +01:00
Jonathan Coates
bfb28b4710 Log current block entity in TickScheduler
This check should be impossible (the BE has not been removed, but is no
longer present in the world), but we've had one instance where it has
happened (#1925). I don't have a good solution here, so at least let's
print both BEs for now.
2024-08-11 12:03:48 +01:00
JackMacWindows
216f0adb3c Fix a couple of typos in fluid method docs
Also mention ffmpeg can now encode/decode DFPWM.
2024-08-11 11:55:11 +01:00
Jonathan Coates
77af4bc213 Fix a couple of typos in fluid method docs 2024-08-11 11:51:40 +01:00
Jonathan Coates
5abab982c7 Allow registering more generic detail providers
Allow registering details providers matching any super type, not just
the exact type. This is mostly useful for 1.21, where we can have
providers for any DataComponentHolder, not just item stacks.
2024-08-11 11:51:40 +01:00
Jonathan Coates
764e1aa332 Merge pull request #1924 from viluon/fix/get-next-version
Fix getNextVersion
2024-08-04 14:50:19 +01:00
Andrew Kvapil
c47718b09d Fix getNextVersion 2024-08-04 14:57:09 +02:00
310 changed files with 9075 additions and 3104 deletions

View File

@@ -18,11 +18,6 @@ ij_any_if_brace_force = if_multiline
ij_any_for_brace_force = if_multiline
ij_any_spaces_within_array_initializer_braces = true
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_method_parameters_wrap = off
ij_kotlin_call_parameters_wrap = off
[*.md]
trim_trailing_whitespace = false
@@ -31,3 +26,16 @@ indent_size = 2
[*.yml]
indent_size = 2
[{*.kt,*.kts}]
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ij_kotlin_continuation_indent_size = 4
ij_kotlin_spaces_around_equality_operators = true
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
# Prefer to handle these manually
ij_kotlin_method_parameters_wrap = off
ij_kotlin_call_parameters_wrap = off
ij_kotlin_extends_list_wrap = off

View File

@@ -6,7 +6,8 @@ body:
id: mc-version
attributes:
label: Minecraft Version
description: What version of Minecraft are you using?
description: |
What version of Minecraft are you using? If your version is not listed, please try to reproduce on one of the supported versions.
options:
- 1.20.1
- 1.21.x
@@ -26,8 +27,5 @@ body:
label: Details
description: |
Description of the bug. Please include the following:
- Logs: These will be located in the `logs/` directory of your Minecraft
instance. Please upload them as a gist or directly into this editor.
- Detailed reproduction steps: sometimes I can spot a bug pretty easily,
but often it's much more obscure. The more information I have to help
reproduce it, the quicker it'll get fixed.
- Logs: These will be located in the `logs/` directory of your Minecraft instance. This is always useful, even if it doesn't include errors, so please upload this!
- Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed.

View File

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

1
.gitignore vendored
View File

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

View File

@@ -27,7 +27,7 @@ repos:
exclude: "^(.*\\.(bat)|LICENSE)$"
- repo: https://github.com/fsfe/reuse-tool
rev: v4.0.3
rev: v5.0.2
hooks:
- id: reuse
@@ -58,6 +58,7 @@ repos:
exclude: |
(?x)^(
projects/[a-z]+/src/generated|
projects/[a-z]+/src/examples/generatedResources|
projects/core/src/test/resources/test-rom/data/json-parsing/|
.*\.dfpwm
)

View File

@@ -12,6 +12,7 @@ If you've any other questions, [just ask the community][community] or [open an i
## Table of Contents
- [Reporting issues](#reporting-issues)
- [Translations](#translations)
- [Setting up a development environment](#setting-up-a-development-environment)
- [Developing CC: Tweaked](#developing-cc-tweaked)
- [Writing documentation](#writing-documentation)
@@ -20,6 +21,9 @@ If you've any other questions, [just ask the community][community] or [open an i
If you have a bug, suggestion, or other feedback, the best thing to do is [file an issue][new-issue]. When doing so, do
use the issue templates - they provide a useful hint on what information to provide.
## Translations
Translations are managed through [CrowdIn], an online interface for managing language strings.
## Setting up a development environment
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
@@ -44,9 +48,12 @@ If you want to run CC:T in a normal Minecraft instance, run `./gradlew assemble`
`projects/forge/build/libs` (for Forge) or `projects/fabric/build/libs` (for Fabric).
## Developing CC: Tweaked
Before making any major changes to CC: Tweaked, I'd recommend you have a read of the [the architecture
document][architecture] first. While it's not a comprehensive document, it gives a good hint of where you should start
looking to make your changes. As always, if you're not sure, [do ask the community][community]!
Before making any major changes to CC: Tweaked, I'd recommend starting opening an issue or starting a discussion on
GitHub first. It's often helpful to discuss features before spending time developing them!
Once you're ready to start programming, have a read of the [the architecture document][architecture] first. While it's
not a comprehensive document, it gives a good hint of where you should start looking to make your changes. As always, if
you're not sure, [do ask the community][community]!
### Testing
When making larger changes, it may be useful to write a test to make sure your code works as expected.
@@ -102,3 +109,4 @@ about how you can build on that until you've covered everything!
[busted]: https://github.com/Olivine-Labs/busted "busted: Elegant Lua unit testing."
[node]: https://nodejs.org/en/ "Node.js"
[architecture]: projects/ARCHITECTURE.md
[Crowdin]: https://crowdin.com/project/cc-tweaked/

View File

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

View File

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

View File

@@ -24,21 +24,19 @@ val mcVersion: String by extra
githubRelease {
token(findProperty("githubApiKey") as String? ?: "")
owner.set("cc-tweaked")
repo.set("CC-Tweaked")
targetCommitish.set(cct.gitBranch)
owner = "cc-tweaked"
repo = "CC-Tweaked"
targetCommitish = cct.gitBranch
tagName.set("v$mcVersion-$modVersion")
releaseName.set("[$mcVersion] $modVersion")
body.set(
provider {
"## " + project(":core").file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
.readLines()
.takeWhile { it != "Type \"help changelog\" to see the full version history." }
.joinToString("\n").trim()
},
)
prerelease.set(isUnstable)
tagName = "v$mcVersion-$modVersion"
releaseName = "[$mcVersion] $modVersion"
body = provider {
"## " + project(":core").file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
.readLines()
.takeWhile { it != "Type \"help changelog\" to see the full version history." }
.joinToString("\n").trim()
}
prerelease = isUnstable
}
tasks.publish { dependsOn(tasks.githubRelease) }
@@ -118,7 +116,7 @@ idea.project.settings.compiler.javac {
}
versionCatalogUpdate {
sortByKey.set(false)
sortByKey = false
pin { versions.addAll("fastutil", "guava", "netty", "slf4j") }
keep { keepUnusedLibraries.set(true) }
keep { keepUnusedLibraries = true }
}

View File

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

View File

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

View File

@@ -10,21 +10,22 @@ import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("net.minecraftforge.gradle")
// We must apply java-convention after Forge, as we need the fg extension to be present.
id("cc-tweaked.java-convention")
id("org.parchmentmc.librarian.forgegradle")
id("net.neoforged.moddev.legacyforge")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
legacyForge {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
version = "${mcVersion}-${libs.findVersion("forge").get()}"
accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
parchment {
minecraftVersion = libs.findVersion("parchmentMc").get().toString()
mappingsVersion = libs.findVersion("parchment").get().toString()
}
}
MinecraftConfigurations.setup(project)
@@ -32,13 +33,3 @@ MinecraftConfigurations.setup(project)
extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = "forge")
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
"minecraft"("net.minecraftforge:forge:$mcVersion-${libs.findVersion("forge").get()}")
}
tasks.configureEach {
// genIntellijRuns isn't registered until much later, so we need this silly hijinks.
if (name == "genIntellijRuns") doLast { IdeaRunConfigurations(project).patch() }
}

View File

@@ -29,7 +29,7 @@ base.archivesName.convention("cc-tweaked-$mcVersion-${project.name}")
java {
toolchain {
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
languageVersion= CCTweakedPlugin.JAVA_VERSION
}
withSourcesJar()
@@ -44,13 +44,6 @@ repositories {
exclusiveContent {
forRepositories(mainMaven)
// Include the ForgeGradle repository if present. This requires that ForgeGradle is already present, which we
// enforce in our Forge overlay.
val fg =
project.extensions.findByType(net.minecraftforge.gradle.userdev.DependencyManagementExtension::class.java)
if (fg != null) forRepositories(fg.repository)
filter {
includeGroup("cc.tweaked")
// Things we mirror
@@ -99,6 +92,7 @@ sourceSets.all {
check("OperatorPrecedence", CheckSeverity.OFF) // For now.
check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid
check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
check("InvalidInlineTag", CheckSeverity.OFF) // Triggered by @snippet. Can be removed on Java 21.
check("NullAway", CheckSeverity.ERROR)
option(
@@ -169,8 +163,8 @@ tasks.test {
}
tasks.withType(JacocoReport::class.java).configureEach {
reports.xml.required.set(true)
reports.html.required.set(true)
reports.xml.required = true
reports.html.required =true
}
project.plugins.withType(CCTweakedPlugin::class.java) {
@@ -226,6 +220,5 @@ idea.module {
// Force Gradle to write to inherit the output directory from the parent, instead of writing to out/xxx/classes.
// This is required for Loom, and we patch Forge's run configurations to work there.
// TODO: Submit a patch to Forge to support ProjectRootManager.
inheritOutputDirs = true
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -143,7 +143,7 @@ fun getNextVersion(version: String): String {
val lastIndex = mainVersion.lastIndexOf('.')
if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"")
val lastVersion = try {
version.substring(lastIndex + 1).toInt()
mainVersion.substring(lastIndex + 1).toInt()
} catch (e: NumberFormatException) {
throw IllegalArgumentException("Cannot parse version format \"$version\"", e)
}

View File

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

View File

@@ -1,73 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.plugins.BasePluginExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.specs.Spec
/**
* A dependency in a POM file.
*/
data class MavenDependency(val groupId: String?, val artifactId: String?, val version: String?, val scope: String?)
/**
* A spec specifying which dependencies to include/exclude.
*/
class MavenDependencySpec {
private val excludeSpecs = mutableListOf<Spec<MavenDependency>>()
fun exclude(spec: Spec<MavenDependency>) {
excludeSpecs.add(spec)
}
fun exclude(dep: Dependency) {
exclude {
// We have to cheat a little for project dependencies, as the project name doesn't match the artifact group.
val name = when (dep) {
is ProjectDependency -> dep.dependencyProject.extensions.getByType(BasePluginExtension::class.java).archivesName.get()
else -> dep.name
}
(dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
(name.isNullOrEmpty() || name == it.artifactId) &&
(dep.version.isNullOrEmpty() || dep.version == it.version)
}
}
fun exclude(dep: MinimalExternalModuleDependency) {
exclude {
dep.module.group == it.groupId && dep.module.name == it.artifactId
}
}
fun isIncluded(dep: MavenDependency) = !excludeSpecs.any { it.isSatisfiedBy(dep) }
}
/**
* Configure dependencies present in this publication's POM file.
*
* While this approach is very ugly, it's the easiest way to handle it!
*/
fun MavenPublication.mavenDependencies(action: MavenDependencySpec.() -> Unit) {
val spec = MavenDependencySpec()
action(spec)
pom.withXml {
val dependencies = XmlUtil.findChild(asNode(), "dependencies") ?: return@withXml
dependencies.children().map { it as groovy.util.Node }.forEach {
val dep = MavenDependency(
groupId = XmlUtil.findChild(it, "groupId")?.text(),
artifactId = XmlUtil.findChild(it, "artifactId")?.text(),
version = XmlUtil.findChild(it, "version")?.text(),
scope = XmlUtil.findChild(it, "scope")?.text(),
)
if (!spec.isIncluded(dep)) it.parent().remove(it)
}
}
}

View File

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

View File

@@ -4,7 +4,7 @@
package cc.tweaked.gradle
import net.minecraftforge.gradle.common.util.RunConfig
import net.neoforged.moddevgradle.internal.RunGameTask
import org.gradle.api.GradleException
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.invocation.Gradle
@@ -65,11 +65,19 @@ abstract class ClientJavaExec : JavaExec() {
setTestProperties()
}
fun copyFromForge(path: String) = copyFromForge(project.tasks.getByName(path, RunGameTask::class))
/**
* Set this task to run a given [RunConfig].
* Set this task to run a given [RunGameTask].
*/
fun setRunConfig(config: RunConfig) {
(this as JavaExec).setRunConfig(config)
fun copyFromForge(task: RunGameTask) {
copyFrom(task)
// Eagerly evaluate the behaviour of RunGameTask.exec
environment.putAll(task.environmentProperty.get())
classpath(task.classpathProvider)
workingDir = task.gameDirectory.get().asFile
setTestProperties() // setRunConfig may clobber some properties, ensure everything is set.
}

View File

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

View File

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

View File

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

View File

@@ -124,7 +124,7 @@ SPDX-License-Identifier: MPL-2.0
</module>
<module name="MethodTypeParameterName" />
<module name="PackageName">
<property name="format" value="^(dan200\.computercraft|cc\.tweaked)(\.[a-z][a-z0-9]*)*" />
<property name="format" value="^(dan200\.computercraft|cc\.tweaked|com\.example\.examplemod)(\.[a-z][a-z0-9]*)*" />
</module>
<module name="ParameterName" />
<module name="StaticVariableName">

28
crowdin.yml Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,14 +26,14 @@ slf4j = "2.0.1"
asm = "9.6"
autoService = "1.1.1"
checkerFramework = "3.42.0"
cobalt = "0.9.3"
cobalt = { strictly = "0.9.5" }
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2"
jzlib = "1.1.3"
kotlin = "1.9.21"
kotlin-coroutines = "1.7.3"
nightConfig = "3.6.7"
kotlin = "2.1.0"
kotlin-coroutines = "1.10.1"
nightConfig = "3.8.1"
# Minecraft mods
emi = "1.0.8+1.20.1"
@@ -52,29 +52,29 @@ create-fabric = "0.5.1-f-build.1467+mc1.20.1"
# Testing
hamcrest = "2.2"
jqwik = "1.8.2"
junit = "5.10.1"
junit = "5.11.4"
junitPlatform = "1.11.4"
jmh = "1.37"
# Build tools
cctJavadoc = "1.8.2"
cctJavadoc = "1.8.3"
checkstyle = "10.14.1"
curseForgeGradle = "1.0.14"
errorProne-core = "2.27.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.7.1"
forgeGradle = "6.0.21"
fabric-loom = "1.9.2"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-73-g43ee16c"
librarian = "1.+"
illuaminate = "0.1.0-74-gf1551d5"
lwjgl = "3.3.3"
minotaur = "2.+"
minotaur = "2.8.7"
modDevGradle = "2.0.74"
nullAway = "0.10.25"
shadow = "8.3.1"
spotless = "6.23.3"
taskTree = "2.1.1"
teavm = "0.11.0-SQUID.1"
vanillaExtract = "0.1.3"
vanillaExtract = "0.2.0"
versionCatalogUpdate = "0.8.1"
[libraries]
@@ -94,9 +94,10 @@ jzlib = { module = "com.jcraft:jzlib", version.ref = "jzlib" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
netty-codec = { module = "io.netty:netty-codec", version.ref = "netty" }
netty-http = { module = "io.netty:netty-codec-http", version.ref = "netty" }
netty-socks = { module = "io.netty:netty-codec-socks", version.ref = "netty" }
netty-proxy = { module = "io.netty:netty-handler-proxy", version.ref = "netty" }
netty-socks = { module = "io.netty:netty-codec-socks", version.ref = "netty" }
nightConfig-core = { module = "com.electronwill.night-config:core", version.ref = "nightConfig" }
nightConfig-toml = { module = "com.electronwill.night-config:toml", version.ref = "nightConfig" }
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
@@ -130,6 +131,7 @@ jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junitPlatform" }
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
jmh = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
jmh-processor = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
@@ -143,19 +145,17 @@ lwjgl-glfw = { module = "org.lwjgl:lwjgl-glfw" }
# Build tools
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
curseForgeGradle = { module = "net.darkhax.curseforgegradle:CurseForgeGradle", version.ref = "curseForgeGradle" }
errorProne-annotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "errorProne-core" }
errorProne-api = { module = "com.google.errorprone:error_prone_check_api", version.ref = "errorProne-core" }
errorProne-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorProne-core" }
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
modDevGradle = { module = "net.neoforged:moddev-gradle", version.ref = "modDevGradle" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
teavm-core = { module = "org.teavm:teavm-core", version.ref = "teavm" }
@@ -170,11 +170,10 @@ vanillaExtract = { module = "cc.tweaked.vanilla-extract:plugin", version.ref = "
yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" }
[plugins]
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
@@ -191,7 +190,7 @@ externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
# Testing
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]
testRuntime = ["junit-jupiter-engine", "junit-platform-launcher", "jqwik-engine"]
# Build tools
teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"]

Binary file not shown.

View File

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

4
gradlew vendored
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

2
gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################

1277
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,10 +13,10 @@
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-typescript": "^11.0.0",
"@rollup/plugin-typescript": "^12.0.0",
"@rollup/plugin-url": "^8.0.1",
"@swc/core": "^1.3.92",
"@types/node": "^20.8.3",
"@types/node": "^22.0.0",
"lightningcss": "^1.22.0",
"preact-render-to-string": "^6.2.1",
"rehype": "^13.0.0",

View File

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

View File

@@ -22,7 +22,14 @@ import java.util.List;
* <p>
* Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a
* modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one
* on Forge
* on Forge.
*
* <h2>Example</h2>
* <h3>Fabric</h3>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
* <h3>Forge</h3>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
* @param <T> The type of turtle upgrade this modeller applies to.
* @see RegisterTurtleUpgradeModeller For multi-loader registration support.

View File

@@ -171,16 +171,9 @@ public final class ComputerCraftAPI {
* using {@link ILuaAPI#getModuleName()} to expose this library as a module instead of as a global.
* <p>
* This may be used with {@link IComputerSystem#getComponent(ComputerComponent)} to only attach APIs to specific
* computers. For example, one can add an additional API just to turtles with the following code:
* computers. For example, one can add a new API just to turtles with the following code:
*
* <pre>{@code
* ComputerCraftAPI.registerAPIFactory(computer -> {
* // Read the turtle component.
* var turtle = computer.getComponent(ComputerComponents.TURTLE);
* // If present then add our API.
* return turtle == null ? null : new MyCustomTurtleApi(turtle);
* });
* }</pre>
* {@snippet class=com.example.examplemod.ExampleAPI region=register}
*
* @param factory The factory for your API subclass.
* @see ILuaAPIFactory

View File

@@ -13,7 +13,7 @@ import java.util.Map;
import java.util.Objects;
/**
* An item detail provider for {@link ItemStack}'s whose {@link Item} has a specific type.
* An item detail provider for {@link ItemStack}s whose {@link Item} has a specific type.
*
* @param <T> The type the stack's item must have.
*/
@@ -22,7 +22,7 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
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}.
* Create a new item detail provider. Details will be inserted into a new sub-map named as per {@code namespace}.
*
* @param itemType The type the stack's item must have.
* @param namespace The namespace to use for this provider.
@@ -34,7 +34,7 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
}
/**
* Create a new item detail provider. Meta will be inserted directly into the results.
* Create a new item detail provider. Details will be inserted directly into the results.
*
* @param itemType The type the stack's item must have.
*/
@@ -53,21 +53,18 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
* @param stack The item stack to provide details for.
* @param item The item to provide details for.
*/
public abstract void provideDetails(
Map<? super String, Object> data, ItemStack stack, T item
);
public abstract void provideDetails(Map<? super String, Object> data, ItemStack stack, T item);
@Override
public void provideDetails(Map<? super String, Object> data, ItemStack stack) {
public final 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));
if (namespace != null) {
if (namespace == null) {
provideDetails(data, stack, itemType.cast(item));
} else {
Map<? super String, Object> child = new HashMap<>();
provideDetails(child, stack, itemType.cast(item));
data.put(namespace, child);
}
}

View File

@@ -26,7 +26,7 @@ public interface DetailRegistry<T> {
* @param provider The detail provider to register.
* @see DetailProvider
*/
void addProvider(DetailProvider<T> provider);
void addProvider(DetailProvider<? super T> provider);
/**
* Compute basic details about an object. This is cheaper than computing all details operation, and so is suitable

View File

@@ -14,7 +14,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
* as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
* for its lifespan.
* <p>
* Elements are generally tied to a block or tile entity in world. In such as case, one should provide the
* Elements are generally tied to a block or block entity in world. In such as case, one should provide the
* {@link WiredElement} capability for the appropriate sides.
*/
public interface WiredElement extends WiredSender {

View File

@@ -14,13 +14,10 @@ import javax.annotation.Nullable;
* A peripheral which can be equipped to the back side of a pocket computer.
* <p>
* Pocket upgrades are defined in two stages. First, on creates a {@link IPocketUpgrade} subclass and corresponding
* {@link PocketUpgradeSerialiser} instance, which are then registered in a Forge registry.
* {@link PocketUpgradeSerialiser} instance, which are then registered in a Minecraft registry.
* <p>
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
* the upgrade registered internally. See the documentation in {@link PocketUpgradeSerialiser} for details on this process
* and where files should be located.
*
* @see PocketUpgradeSerialiser For how to register a pocket computer upgrade.
* the upgrade registered internally.
*/
public interface IPocketUpgrade extends UpgradeBase {
/**

View File

@@ -8,21 +8,69 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.Items;
import javax.annotation.Nullable;
import java.util.function.BiFunction;
/**
* The primary interface for defining an update for Turtles. A turtle update can either be a new tool, or a new
* peripheral.
* <p>
* Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding
* {@link TurtleUpgradeSerialiser} instance, which are then registered in a Forge registry.
* {@link TurtleUpgradeSerialiser} instance, which are then registered in a Minecraft registry.
* <p>
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
* the upgrade registered internally. See the documentation in {@link TurtleUpgradeSerialiser} for details on this process
* and where files should be located.
* the upgrade automatically registered.
*
* @see TurtleUpgradeSerialiser For how to register a turtle upgrade.
* <h2>Example</h2>
* <h3>Registering the upgrade serialiser</h3>
* First, let's create a new class that implements {@link ITurtleUpgrade}. It is recommended to subclass
* {@link AbstractTurtleUpgrade}, as that provides a default implementation of most methods.
* <p>
* {@snippet class=com.example.examplemod.ExampleTurtleUpgrade region=body}
* <p>
* Now we must construct a new upgrade serialiser. In most cases, you can use one of the helper methods
* (e.g. {@link TurtleUpgradeSerialiser#simpleWithCustomItem(BiFunction)}), rather than defining your own implementation.
*
* {@snippet class=com.example.examplemod.ExampleMod region=turtle_upgrades}
*
* We now must register this upgrade serialiser. This is done the same way as you'd register blocks, items, or other
* Minecraft objects. The approach to do this will depend on mod-loader.
*
* <h4>Fabric</h4>
* {@snippet class=com.example.examplemod.FabricExampleMod region=turtle_upgrades}
*
* <h4>Forge</h4>
* {@snippet class=com.example.examplemod.ForgeExampleMod region=turtle_upgrades}
*
* <h3>Rendering the upgrade</h3>
* Next, we need to register a model for our upgrade. This is done by registering a
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for your upgrade serialiser.
*
* <h4>Fabric</h4>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
*
* <h4>Forge</h4>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
* <h3>Registering the upgrade itself</h3>
* Upgrades themselves are loaded from datapacks when a level is loaded. In order to register our new upgrade, we must
* create a new JSON file at {@code data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}.
*
* {@snippet file = data/examplemod/computercraft/turtle_upgrades/example_turtle_upgrade.json}
*
* The {@code "type"} field points to the ID of the upgrade serialiser we've just registered, while the other fields
* are read by the serialiser itself. As our upgrade was defined with {@link TurtleUpgradeSerialiser#simpleWithCustomItem(BiFunction)}, the
* {@code "item"} field will construct our upgrade with {@link Items#COMPASS}.
* <p>
* Rather than manually creating the file, it is recommended to data-generators to generate this file. This can be done
* with {@link TurtleUpgradeDataProvider}.
*
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
*
* @see TurtleUpgradeSerialiser Registering a turtle upgrade.
*/
public interface ITurtleUpgrade extends UpgradeBase {
/**

View File

@@ -5,7 +5,7 @@
package dan200.computercraft.api.turtle;
/**
* An enum representing the two sides of the turtle that a turtle turtle might reside.
* An enum representing the two sides of the turtle that a turtle upgrade might reside.
*/
public enum TurtleSide {
/**

View File

@@ -29,6 +29,9 @@ import java.util.function.Consumer;
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
* generate them.
*
* <h2>Example</h2>
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
*
* @see TurtleUpgradeSerialiser
*/
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {

View File

@@ -27,32 +27,6 @@ import java.util.function.Function;
* If your turtle upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
* {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
*
* <h2>Example (Forge)</h2>
* <pre>{@code
* static final DeferredRegister<TurtleUpgradeSerialiser<?>> SERIALISERS = DeferredRegister.create( TurtleUpgradeSerialiser.TYPE, "my_mod" );
*
* // Register a new upgrade serialiser called "my_upgrade".
* public static final RegistryObject<TurtleUpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
* SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
*
* // Then in your constructor
* SERIALISERS.register( bus );
* }</pre>
* <p>
* We can then define a new upgrade using JSON by placing the following in
* {@literal data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}}.
*
* <pre>{@code
* {
* "type": my_mod:my_upgrade",
* }
* }</pre>
* <p>
* Finally, we need to register a model for our upgrade. The way to do this varies on mod loader, see
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for more information.
* <p>
* {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
*
* @param <T> The type of turtle upgrade this is responsible for serialising.
* @see ITurtleUpgrade
* @see TurtleUpgradeDataProvider

View File

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

View File

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

View File

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

View File

@@ -64,6 +64,9 @@ public final class ClientHooks {
public static void onWorldUnload() {
MonitorRenderState.destroyAll();
SpeakerManager.reset();
}
public static void onDisconnect() {
ClientPocketComputers.reset();
}

View File

@@ -13,6 +13,7 @@ import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.CustomLecternRenderer;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
@@ -73,6 +74,7 @@ public final class ClientRegistry {
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new);
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new);
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), TurtleBlockEntityRenderer::new);
BlockEntityRenderers.register(ModRegistry.BlockEntities.LECTERN.get(), CustomLecternRenderer::new);
}
/**

View File

@@ -6,7 +6,6 @@ package dan200.computercraft.client.gui;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.render.ComputerBorderRenderer;
import dan200.computercraft.data.client.ClientDataProviders;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureManager;
@@ -113,7 +112,6 @@ public final class GuiSprites extends TextureAtlasHolder {
* @param pocketBottom The texture for the bottom of a pocket computer.
* @param sidebar The texture for the computer sidebar.
* @see ComputerBorderRenderer
* @see ClientDataProviders
*/
public record ComputerTextures(
ResourceLocation border,

View File

@@ -6,15 +6,22 @@ package dan200.computercraft.client.gui;
import com.mojang.blaze3d.vertex.Tesselator;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.common.HeldItemMenu;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.media.PrintoutMenu;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerListener;
import net.minecraft.world.item.ItemStack;
import org.lwjgl.glfw.GLFW;
import java.util.Arrays;
import java.util.Objects;
import static dan200.computercraft.client.render.PrintoutRenderer.*;
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
@@ -23,40 +30,75 @@ import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMA
*
* @see dan200.computercraft.client.render.PrintoutRenderer
*/
public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
private final boolean book;
private final int pages;
private final TextBuffer[] text;
private final TextBuffer[] colours;
private int page;
public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu> implements ContainerListener {
private PrintoutInfo printout = PrintoutInfo.DEFAULT;
private int page = 0;
public PrintoutScreen(HeldItemMenu container, Inventory player, Component title) {
public PrintoutScreen(PrintoutMenu container, Inventory player, Component title) {
super(container, player, title);
imageHeight = Y_SIZE;
}
var text = PrintoutItem.getText(container.getStack());
this.text = new TextBuffer[text.length];
for (var i = 0; i < this.text.length; i++) this.text[i] = new TextBuffer(text[i]);
private void setPrintout(ItemStack stack) {
var text = PrintoutItem.getText(stack);
var textBuffers = new TextBuffer[text.length];
for (var i = 0; i < textBuffers.length; i++) textBuffers[i] = new TextBuffer(text[i]);
var colours = PrintoutItem.getColours(container.getStack());
this.colours = new TextBuffer[colours.length];
for (var i = 0; i < this.colours.length; i++) this.colours[i] = new TextBuffer(colours[i]);
var colours = PrintoutItem.getColours(stack);
var colourBuffers = new TextBuffer[colours.length];
for (var i = 0; i < colours.length; i++) colourBuffers[i] = new TextBuffer(colours[i]);
page = 0;
pages = Math.max(this.text.length / PrintoutItem.LINES_PER_PAGE, 1);
book = ((PrintoutItem) container.getStack().getItem()).getType() == PrintoutItem.Type.BOOK;
var pages = Math.max(text.length / PrintoutItem.LINES_PER_PAGE, 1);
var book = stack.is(ModRegistry.Items.PRINTED_BOOK.get());
printout = new PrintoutInfo(pages, book, textBuffers, colourBuffers);
}
@Override
protected void init() {
super.init();
menu.addSlotListener(this);
}
@Override
public void removed() {
menu.removeSlotListener(this);
}
@Override
public void slotChanged(AbstractContainerMenu menu, int slot, ItemStack stack) {
if (slot == 0) setPrintout(stack);
}
@Override
public void dataChanged(AbstractContainerMenu menu, int slot, int data) {
if (slot == PrintoutMenu.DATA_CURRENT_PAGE) page = data;
}
private void setPage(int page) {
this.page = page;
var gameMode = Objects.requireNonNull(Objects.requireNonNull(minecraft).gameMode);
gameMode.handleInventoryButtonClick(menu.containerId, PrintoutMenu.PAGE_BUTTON_OFFSET + page);
}
private void previousPage() {
if (page > 0) setPage(page - 1);
}
private void nextPage() {
if (page < printout.pages() - 1) setPage(page + 1);
}
@Override
public boolean keyPressed(int key, int scancode, int modifiers) {
if (key == GLFW.GLFW_KEY_RIGHT) {
if (page < pages - 1) page++;
nextPage();
return true;
}
if (key == GLFW.GLFW_KEY_LEFT) {
if (page > 0) page--;
previousPage();
return true;
}
@@ -68,13 +110,13 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
if (super.mouseScrolled(x, y, delta)) return true;
if (delta < 0) {
// Scroll up goes to the next page
if (page < pages - 1) page++;
nextPage();
return true;
}
if (delta > 0) {
// Scroll down goes to the previous page
if (page > 0) page--;
previousPage();
return true;
}
@@ -85,8 +127,9 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
// Draw the printout
var renderer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP);
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours);
drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, printout.text(), printout.colour());
renderer.endBatch();
}
@@ -105,4 +148,18 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
// Skip rendering labels.
}
record PrintoutInfo(int pages, boolean book, TextBuffer[] text, TextBuffer[] colour) {
public static final PrintoutInfo DEFAULT;
static {
var textLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
Arrays.fill(textLines, new TextBuffer(" ".repeat(PrintoutItem.LINE_MAX_LENGTH)));
var colourLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
Arrays.fill(colourLines, new TextBuffer("f".repeat(PrintoutItem.LINE_MAX_LENGTH)));
DEFAULT = new PrintoutInfo(1, false, textLines, colourLines);
}
}
}

View File

@@ -0,0 +1,117 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.render.CustomLecternRenderer;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.resources.model.Material;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.inventory.InventoryMenu;
import java.util.List;
/**
* A model for {@linkplain PrintoutItem printouts} placed on a lectern.
* <p>
* This provides two models, {@linkplain #renderPages(PoseStack, VertexConsumer, int, int, int) one for a variable
* number of pages}, and {@linkplain #renderBook(PoseStack, VertexConsumer, int, int) one for books}.
*
* @see CustomLecternRenderer
*/
public class LecternPrintoutModel {
public static final ResourceLocation TEXTURE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/printout");
public static final Material MATERIAL = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE);
private static final int TEXTURE_WIDTH = 32;
private static final int TEXTURE_HEIGHT = 32;
private static final String PAGE_1 = "page_1";
private static final String PAGE_2 = "page_2";
private static final String PAGE_3 = "page_3";
private static final List<String> PAGES = List.of(PAGE_1, PAGE_2, PAGE_3);
private final ModelPart pagesRoot;
private final ModelPart bookRoot;
private final ModelPart[] pages;
public LecternPrintoutModel() {
pagesRoot = buildPages();
bookRoot = buildBook();
pages = PAGES.stream().map(pagesRoot::getChild).toArray(ModelPart[]::new);
}
private static ModelPart buildPages() {
var mesh = new MeshDefinition();
var parts = mesh.getRoot();
parts.addOrReplaceChild(
PAGE_1,
CubeListBuilder.create().texOffs(0, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
PartPose.ZERO
);
parts.addOrReplaceChild(
PAGE_2,
CubeListBuilder.create().texOffs(12, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
PartPose.offsetAndRotation(-0.125f, 0, 1.5f, (float) Math.PI * (1f / 16), 0, 0)
);
parts.addOrReplaceChild(
PAGE_3,
CubeListBuilder.create().texOffs(12, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
PartPose.offsetAndRotation(-0.25f, 0, -1.5f, (float) -Math.PI * (2f / 16), 0, 0)
);
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
}
private static ModelPart buildBook() {
var mesh = new MeshDefinition();
var parts = mesh.getRoot();
parts.addOrReplaceChild(
"spine",
CubeListBuilder.create().texOffs(12, 15).addBox(-0.005f, -5.0f, -0.5f, 0, 10, 1.0f),
PartPose.ZERO
);
var angle = (float) Math.toRadians(5);
parts.addOrReplaceChild(
"left",
CubeListBuilder.create()
.texOffs(0, 10).addBox(0, -5.0f, -6.0f, 0, 10, 6.0f)
.texOffs(0, 0).addBox(0.005f, -4.0f, -5.0f, 1.0f, 8.0f, 5.0f),
PartPose.offsetAndRotation(-0.005f, 0, -0.5f, 0, -angle, 0)
);
parts.addOrReplaceChild(
"right",
CubeListBuilder.create()
.texOffs(14, 10).addBox(0, -5.0f, 0, 0, 10, 6.0f)
.texOffs(0, 0).addBox(0.005f, -4.0f, 0, 1.0f, 8.0f, 5.0f),
PartPose.offsetAndRotation(-0.005f, 0, 0.5f, 0, angle, 0)
);
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
}
public void renderBook(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay) {
bookRoot.render(poseStack, buffer, packedLight, packedOverlay, 1, 1, 1, 1);
}
public void renderPages(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay, int pageCount) {
if (pageCount > pages.length) pageCount = pages.length;
var i = 0;
for (; i < pageCount; i++) pages[i].visible = true;
for (; i < pages.length; i++) pages[i].visible = false;
pagesRoot.render(poseStack, buffer, packedLight, packedOverlay, 1, 1, 1, 1);
}
}

View File

@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import dan200.computercraft.client.model.LecternPrintoutModel;
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.blockentity.LecternRenderer;
import net.minecraft.world.level.block.LecternBlock;
/**
* A block entity renderer for our {@linkplain CustomLecternBlockEntity lectern}.
* <p>
* This largely follows {@link LecternRenderer}, but with support for multiple types of item.
*/
public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternBlockEntity> {
private final LecternPrintoutModel printoutModel;
public CustomLecternRenderer(BlockEntityRendererProvider.Context context) {
printoutModel = new LecternPrintoutModel();
}
@Override
public void render(CustomLecternBlockEntity lectern, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) {
poseStack.pushPose();
poseStack.translate(0.5f, 1.0625f, 0.5f);
poseStack.mulPose(Axis.YP.rotationDegrees(-lectern.getBlockState().getValue(LecternBlock.FACING).getClockWise().toYRot()));
poseStack.mulPose(Axis.ZP.rotationDegrees(67.5f));
poseStack.translate(0, -0.125f, 0);
var item = lectern.getItem();
if (item.getItem() instanceof PrintoutItem printout) {
var vertexConsumer = LecternPrintoutModel.MATERIAL.buffer(buffer, RenderType::entitySolid);
if (printout.getType() == PrintoutItem.Type.BOOK) {
printoutModel.renderBook(poseStack, vertexConsumer, packedLight, packedOverlay);
} else {
printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutItem.getPageCount(item));
}
}
poseStack.popPose();
}
}

View File

@@ -13,6 +13,7 @@ import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.util.ARGB32;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4f;
@@ -92,16 +93,11 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
}
private static void renderLight(PoseStack transform, MultiBufferSource render, int colour, int width, int height) {
var r = (byte) ((colour >>> 16) & 0xFF);
var g = (byte) ((colour >>> 8) & 0xFF);
var b = (byte) (colour & 0xFF);
var c = new byte[]{ r, g, b, (byte) 255 };
var buffer = render.getBuffer(RenderTypes.TERMINAL);
FixedWidthFontRenderer.drawQuad(
FixedWidthFontRenderer.toVertexConsumer(transform, buffer),
width - LIGHT_HEIGHT * 2, height + BORDER / 2.0f, 0.001f, LIGHT_HEIGHT * 2, LIGHT_HEIGHT,
c, RenderTypes.FULL_BRIGHT_LIGHTMAP
ARGB32.opaque(colour), RenderTypes.FULL_BRIGHT_LIGHTMAP
);
}
}

View File

@@ -176,7 +176,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
var size = DirectFixedWidthFontRenderer.getVertexCount(terminal);
// In an ideal world we could upload these both into one buffer. However, we can't render VBOs with
// and starting and ending offset, and so need to use two buffers instead.
// a starting and ending offset, and so need to use two buffers instead.
renderToBuffer(backgroundBuffer, size, sink ->
DirectFixedWidthFontRenderer.drawTerminalBackground(sink, 0, 0, terminal, yMargin, yMargin, xMargin, xMargin));
@@ -208,10 +208,10 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
foregroundBuffer.bind();
foregroundBuffer.drawWithShader(
matrix, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader(),
// As mentioned in the above comment, render the extra cursor quad if it is visible this frame. Each
// // quad has an index count of 6.
FixedWidthFontRenderer.isCursorVisible(terminal) && FrameInfo.getGlobalCursorBlink()
? foregroundBuffer.getIndexCount() + 6 : foregroundBuffer.getIndexCount()
// Skip the cursor quad if it is not visible this frame.
FixedWidthFontRenderer.isCursorVisible(terminal) && !FrameInfo.getGlobalCursorBlink()
? foregroundBuffer.getIndexCount() - RenderTypes.TERMINAL.mode().indexCount(4)
: foregroundBuffer.getIndexCount()
);
// Clear state

View File

@@ -13,9 +13,11 @@ import dan200.computercraft.core.terminal.Palette;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.util.ARGB32;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.*;
import static org.lwjgl.system.MemoryUtil.*;
@@ -38,10 +40,12 @@ import static org.lwjgl.system.MemoryUtil.*;
* {@link FixedWidthFontRenderer}.
*/
public final class DirectFixedWidthFontRenderer {
private static final boolean IS_LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
private DirectFixedWidthFontRenderer() {
}
private static void drawChar(QuadEmitter emitter, float x, float y, int index, byte[] colour) {
private static void drawChar(QuadEmitter emitter, float x, float y, int index, int colour) {
// Short circuit to avoid the common case - the texture should be blank here after all.
if (index == '\0' || index == ' ') return;
@@ -158,8 +162,8 @@ public final class DirectFixedWidthFontRenderer {
return (terminal.getHeight() + 2) * (terminal.getWidth() + 2) * 2;
}
private static void quad(QuadEmitter buffer, float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) {
buffer.quad(x1, y1, x2, y2, z, rgba, u1, v1, u2, v2);
private static void quad(QuadEmitter buffer, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
buffer.quad(x1, y1, x2, y2, z, colour, u1, v1, u2, v2);
}
public interface QuadEmitter {
@@ -167,7 +171,7 @@ public final class DirectFixedWidthFontRenderer {
ByteBuffer buffer();
void quad(float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2);
void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2);
}
public record ByteBufferEmitter(ByteBuffer buffer) implements QuadEmitter {
@@ -177,12 +181,12 @@ public final class DirectFixedWidthFontRenderer {
}
@Override
public void quad(float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) {
DirectFixedWidthFontRenderer.quad(buffer, x1, y1, x2, y2, z, rgba, u1, v1, u2, v2);
public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
DirectFixedWidthFontRenderer.quad(buffer, x1, y1, x2, y2, z, colour, u1, v1, u2, v2);
}
}
static void quad(ByteBuffer buffer, float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) {
static void quad(ByteBuffer buffer, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
// Emit a single quad to our buffer. This uses Unsafe (well, LWJGL's MemoryUtil) to directly blit bytes to the
// underlying buffer. This allows us to have a single bounds check up-front, rather than one for every write.
// This provides significant performance gains, at the cost of well, using Unsafe.
@@ -196,16 +200,15 @@ public final class DirectFixedWidthFontRenderer {
if (position < 0 || 112 > buffer.limit() - position) throw new IndexOutOfBoundsException();
// Require the pointer to be aligned to a 32-bit boundary.
if ((addr & 3) != 0) throw new IllegalStateException("Memory is not aligned");
// Also assert the length of the array. This appears to help elide bounds checks on the array in some circumstances.
if (rgba.length != 4) throw new IllegalStateException();
// Pack colour so it is equivalent to rgba:BBBB.
var colourAbgr = ARGB32.toABGR32(colour);
var nativeColour = IS_LITTLE_ENDIAN ? colourAbgr : Integer.reverseBytes(colourAbgr);
memPutFloat(addr + 0, x1);
memPutFloat(addr + 4, y1);
memPutFloat(addr + 8, z);
memPutByte(addr + 12, rgba[0]);
memPutByte(addr + 13, rgba[1]);
memPutByte(addr + 14, rgba[2]);
memPutByte(addr + 15, (byte) 255);
memPutInt(addr + 12, nativeColour);
memPutFloat(addr + 16, u1);
memPutFloat(addr + 20, v1);
memPutShort(addr + 24, (short) 0xF0);
@@ -214,10 +217,7 @@ public final class DirectFixedWidthFontRenderer {
memPutFloat(addr + 28, x1);
memPutFloat(addr + 32, y2);
memPutFloat(addr + 36, z);
memPutByte(addr + 40, rgba[0]);
memPutByte(addr + 41, rgba[1]);
memPutByte(addr + 42, rgba[2]);
memPutByte(addr + 43, (byte) 255);
memPutInt(addr + 40, nativeColour);
memPutFloat(addr + 44, u1);
memPutFloat(addr + 48, v2);
memPutShort(addr + 52, (short) 0xF0);
@@ -226,10 +226,7 @@ public final class DirectFixedWidthFontRenderer {
memPutFloat(addr + 56, x2);
memPutFloat(addr + 60, y2);
memPutFloat(addr + 64, z);
memPutByte(addr + 68, rgba[0]);
memPutByte(addr + 69, rgba[1]);
memPutByte(addr + 70, rgba[2]);
memPutByte(addr + 71, (byte) 255);
memPutInt(addr + 68, nativeColour);
memPutFloat(addr + 72, u2);
memPutFloat(addr + 76, v2);
memPutShort(addr + 80, (short) 0xF0);
@@ -238,10 +235,7 @@ public final class DirectFixedWidthFontRenderer {
memPutFloat(addr + 84, x2);
memPutFloat(addr + 88, y1);
memPutFloat(addr + 92, z);
memPutByte(addr + 96, rgba[0]);
memPutByte(addr + 97, rgba[1]);
memPutByte(addr + 98, rgba[2]);
memPutByte(addr + 99, (byte) 255);
memPutInt(addr + 96, nativeColour);
memPutFloat(addr + 100, u2);
memPutFloat(addr + 104, v1);
memPutShort(addr + 108, (short) 0xF0);

View File

@@ -12,6 +12,7 @@ import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.core.util.Colour;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FastColor;
import org.joml.Matrix4f;
import org.joml.Vector3f;
@@ -41,7 +42,7 @@ public final class FixedWidthFontRenderer {
static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH;
static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH;
private static final byte[] BLACK = new byte[]{ byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()), (byte) 255 };
private static final int BLACK = FastColor.ARGB32.color(255, byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()));
private static final float Z_OFFSET = 1e-3f;
private FixedWidthFontRenderer() {
@@ -59,7 +60,7 @@ public final class FixedWidthFontRenderer {
return 15 - Terminal.getColour(c, def);
}
private static void drawChar(QuadEmitter emitter, float x, float y, int index, byte[] colour, int light) {
private static void drawChar(QuadEmitter emitter, float x, float y, int index, int colour, int light) {
// Short circuit to avoid the common case - the texture should be blank here after all.
if (index == '\0' || index == ' ') return;
@@ -75,7 +76,7 @@ public final class FixedWidthFontRenderer {
);
}
public static void drawQuad(QuadEmitter emitter, float x, float y, float z, float width, float height, byte[] colour, int light) {
public static void drawQuad(QuadEmitter emitter, float x, float y, float z, float width, float height, int colour, int light) {
quad(emitter, x, y, x + width, y + height, z, colour, BACKGROUND_START, BACKGROUND_START, BACKGROUND_END, BACKGROUND_END, light);
}
@@ -216,10 +217,10 @@ public final class FixedWidthFontRenderer {
return new QuadEmitter(transform.last().pose(), consumer);
}
private static void quad(QuadEmitter c, float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2, int light) {
private static void quad(QuadEmitter c, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2, int light) {
var poseMatrix = c.poseMatrix();
var consumer = c.consumer();
byte r = rgba[0], g = rgba[1], b = rgba[2], a = rgba[3];
int r = FastColor.ARGB32.red(colour), g = FastColor.ARGB32.green(colour), b = FastColor.ARGB32.blue(colour), a = FastColor.ARGB32.alpha(colour);
consumer.vertex(poseMatrix, x1, y1, z).color(r, g, b, a).uv(u1, v1).uv2(light).endVertex();
consumer.vertex(poseMatrix, x1, y2, z).color(r, g, b, a).uv(u1, v2).uv2(light).endVertex();

View File

@@ -1,48 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.client;
import dan200.computercraft.client.gui.GuiSprites;
import dan200.computercraft.data.DataProviders;
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
/**
* A version of {@link DataProviders} which relies on client-side classes.
* <p>
* This is called from {@link DataProviders#add(DataProviders.GeneratorSink)}.
*/
public final class ClientDataProviders {
private ClientDataProviders() {
}
public static void add(DataProviders.GeneratorSink generator) {
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
out.accept(new ResourceLocation("blocks"), List.of(
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty())
));
out.accept(GuiSprites.SPRITE_SHEET, Stream.of(
// Buttons
GuiSprites.TURNED_OFF.textures(),
GuiSprites.TURNED_ON.textures(),
GuiSprites.TERMINATE.textures(),
// Computers
GuiSprites.COMPUTER_NORMAL.textures(),
GuiSprites.COMPUTER_ADVANCED.textures(),
GuiSprites.COMPUTER_COMMAND.textures(),
GuiSprites.COMPUTER_COLOUR.textures()
).flatMap(x -> x).<SpriteSource>map(x -> new SingleFile(x, Optional.empty())).toList());
});
}
}

View File

@@ -23,6 +23,7 @@ import net.minecraft.data.models.BlockModelGenerators;
import net.minecraft.data.models.blockstates.*;
import net.minecraft.data.models.model.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.Property;
@@ -96,10 +97,17 @@ class BlockModelProvider {
registerCable(generators);
registerRedstoneControl(generators);
registerTurtleUpgrade(generators, "block/turtle_crafting_table", "block/turtle_crafty_face");
registerTurtleUpgrade(generators, "block/turtle_speaker", "block/turtle_speaker_face");
registerTurtleModem(generators, "block/turtle_modem_normal", "block/wireless_modem_normal_face");
registerTurtleModem(generators, "block/turtle_modem_advanced", "block/wireless_modem_advanced_face");
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(
ModRegistry.Blocks.LECTERN.get(),
Variant.variant().with(VariantProperties.MODEL, ModelLocationUtils.getModelLocation(Blocks.LECTERN))
).with(createHorizontalFacingDispatch()));
}
private static void registerDiskDrive(BlockModelGenerators generators) {
@@ -349,6 +357,18 @@ class BlockModelProvider {
generators.blockStateOutput.accept(generator);
}
private static void registerRedstoneControl(BlockModelGenerators generators) {
var redstoneControl = ModRegistry.Blocks.REDSTONE_RELAY.get();
var model = ModelTemplates.CUBE_ORIENTABLE_TOP_BOTTOM.create(
redstoneControl, TextureMapping.orientableCube(redstoneControl), generators.modelOutput
);
generators.blockStateOutput.accept(
MultiVariantGenerator.multiVariant(redstoneControl, Variant.variant().with(VariantProperties.MODEL, model))
.with(createHorizontalFacingDispatch())
);
}
private static final BooleanProperty[] CABLE_DIRECTIONS = { CableBlock.DOWN, CableBlock.UP, CableBlock.NORTH, CableBlock.SOUTH, CableBlock.WEST, CableBlock.EAST };
private static final boolean[] BOOLEANS = new boolean[]{ false, true };

View File

@@ -5,6 +5,12 @@
package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import dan200.computercraft.client.gui.GuiSprites;
import dan200.computercraft.client.model.LecternPrintoutModel;
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.tags.TagsProvider;
@@ -13,9 +19,14 @@ import net.minecraft.server.packs.PackType;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* All data providers for ComputerCraft. We require a mod-loader abstraction {@link GeneratorSink} (instead of
@@ -39,14 +50,30 @@ public final class DataProviders {
generator.add(out -> new LanguageProvider(out, turtleUpgrades, pocketUpgrades));
// Unfortunately we rely on some client-side classes in this code. We just load in the client side data provider
// and invoke that.
try {
Class.forName("dan200.computercraft.data.client.ClientDataProviders")
.getMethod("add", GeneratorSink.class).invoke(null, generator);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
out.accept(new ResourceLocation("blocks"), makeSprites(Stream.of(
UpgradeSlot.LEFT_UPGRADE,
UpgradeSlot.RIGHT_UPGRADE,
LecternPrintoutModel.TEXTURE
)));
out.accept(GuiSprites.SPRITE_SHEET, makeSprites(
// Buttons
GuiSprites.TURNED_OFF.textures(),
GuiSprites.TURNED_ON.textures(),
GuiSprites.TERMINATE.textures(),
// Computers
GuiSprites.COMPUTER_NORMAL.textures(),
GuiSprites.COMPUTER_ADVANCED.textures(),
GuiSprites.COMPUTER_COMMAND.textures(),
GuiSprites.COMPUTER_COLOUR.textures()
));
});
}
@SafeVarargs
@SuppressWarnings("varargs")
private static List<SpriteSource> makeSprites(final Stream<ResourceLocation>... files) {
return Arrays.stream(files).flatMap(Function.identity()).<SpriteSource>map(x -> new SingleFile(x, Optional.empty())).toList();
}
public interface GeneratorSink {

View File

@@ -80,6 +80,7 @@ public final class LanguageProvider implements DataProvider {
add(ModRegistry.Items.WIRED_MODEM.get(), "Wired Modem");
add(ModRegistry.Items.CABLE.get(), "Networking Cable");
add(ModRegistry.Items.WIRED_MODEM_FULL.get(), "Wired Modem");
add(ModRegistry.Items.REDSTONE_RELAY.get(), "Redstone Relay");
add(ModRegistry.Items.TURTLE_NORMAL.get(), "Turtle");
add(ModRegistry.Blocks.TURTLE_NORMAL.get().getDescriptionId() + ".upgraded", "%s Turtle");
@@ -160,8 +161,6 @@ public final class LanguageProvider implements DataProvider {
add("commands.computercraft.queue.synopsis", "Send a computer_command event to a command computer");
add("commands.computercraft.queue.desc", "Send a computer_command event to a command computer, passing through the additional arguments. This is mostly designed for map makers, acting as a more computer-friendly version of /trigger. Any player can run the command, which would most likely be done through a text component's click event.");
add("commands.computercraft.generic.no_position", "<no pos>");
add("commands.computercraft.generic.position", "%s, %s, %s");
add("commands.computercraft.generic.yes", "Y");
add("commands.computercraft.generic.no", "N");
add("commands.computercraft.generic.exception", "Unhandled exception (%s)");
@@ -284,7 +283,9 @@ public final class LanguageProvider implements DataProvider {
return Stream.of(
RegistryWrappers.BLOCKS.stream()
.filter(x -> RegistryWrappers.BLOCKS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
.map(Block::getDescriptionId),
.map(Block::getDescriptionId)
// Exclude blocks that just reuse vanilla translations, such as the lectern.
.filter(x -> !x.startsWith("block.minecraft.")),
RegistryWrappers.ITEMS.stream()
.filter(x -> RegistryWrappers.ITEMS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
.map(Item::getDescriptionId),

View File

@@ -15,6 +15,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
import net.minecraft.advancements.critereon.StatePropertiesPredicate;
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.LootTable;
@@ -50,6 +51,7 @@ class LootTableProvider {
selfDrop(add, ModRegistry.Blocks.WIRED_MODEM_FULL);
selfDrop(add, ModRegistry.Blocks.WIRELESS_MODEM_NORMAL);
selfDrop(add, ModRegistry.Blocks.WIRELESS_MODEM_ADVANCED);
selfDrop(add, ModRegistry.Blocks.REDSTONE_RELAY);
computerDrop(add, ModRegistry.Blocks.COMPUTER_NORMAL);
computerDrop(add, ModRegistry.Blocks.COMPUTER_ADVANCED);
@@ -57,6 +59,8 @@ class LootTableProvider {
computerDrop(add, ModRegistry.Blocks.TURTLE_NORMAL);
computerDrop(add, ModRegistry.Blocks.TURTLE_ADVANCED);
blockDrop(add, ModRegistry.Blocks.LECTERN, LootItem.lootTableItem(Items.LECTERN), ExplosionCondition.survivesExplosion());
add.accept(ModRegistry.Blocks.CABLE.get().getLootTable(), LootTable
.lootTable()
.withPool(LootPool.lootPool()

View File

@@ -7,6 +7,7 @@ package dan200.computercraft.data;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import dan200.computercraft.shared.util.PrettyJsonWriter;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;

View File

@@ -461,6 +461,17 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.requires(ingredients.string())
.unlockedBy("has_printer", inventoryChange(ModRegistry.Blocks.PRINTER.get()))
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(), add));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.REDSTONE_RELAY.get())
.pattern("SRS")
.pattern("RCR")
.pattern("SRS")
.define('S', Items.STONE)
.define('R', ingredients.redstone())
.define('C', ModRegistry.Blocks.CABLE.get())
.unlockedBy("has_cable", inventoryChange(ModRegistry.Blocks.CABLE.get()))
.save(add);
}
private static DyeColor ofColour(Colour colour) {

View File

@@ -78,9 +78,12 @@ class TagProvider {
ModRegistry.Blocks.WIRELESS_MODEM_NORMAL.get(),
ModRegistry.Blocks.WIRELESS_MODEM_ADVANCED.get(),
ModRegistry.Blocks.WIRED_MODEM_FULL.get(),
ModRegistry.Blocks.CABLE.get()
ModRegistry.Blocks.CABLE.get(),
ModRegistry.Blocks.REDSTONE_RELAY.get()
);
tags.tag(BlockTags.MINEABLE_WITH_AXE).add(ModRegistry.Blocks.LECTERN.get());
tags.tag(BlockTags.WITHER_IMMUNE).add(ModRegistry.Blocks.COMPUTER_COMMAND.get());
tags.tag(ExternalModTags.Blocks.CREATE_BRITTLE).add(
@@ -102,8 +105,14 @@ class TagProvider {
ModRegistry.Items.MONITOR_ADVANCED.get()
);
// Allow printed books to be placed in bookshelves.
tags.tag(ItemTags.BOOKSHELF_BOOKS).add(ModRegistry.Items.PRINTED_BOOK.get());
// Allow any printout to be placed on lecterns. See also PrintoutItem and CustomLecternBlock.
tags.tag(ItemTags.LECTERN_BOOKS).add(
ModRegistry.Items.PRINTED_PAGE.get(), ModRegistry.Items.PRINTED_PAGES.get(), ModRegistry.Items.PRINTED_BOOK.get()
);
tags.tag(ComputerCraftTags.Items.TURTLE_CAN_PLACE)
.add(Items.GLASS_BOTTLE)
.addTag(ItemTags.BOATS);
@@ -139,7 +148,7 @@ class TagProvider {
/**
* A wrapper over {@link ItemTagsProvider}.
*/
interface ItemTagConsumer extends TagConsumer<Item> {
public interface ItemTagConsumer extends TagConsumer<Item> {
void copy(TagKey<Block> block, TagKey<Item> item);
}
}

View File

@@ -0,0 +1,4 @@
{
"type": "examplemod:example_turtle_upgrade",
"item": "minecraft:compass"
}

View File

@@ -0,0 +1,75 @@
package com.example.examplemod;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.component.ComputerComponents;
import dan200.computercraft.api.lua.Coerced;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.turtle.ITurtleAccess;
import org.jetbrains.annotations.Nullable;
/**
* An example API that will be available on every turtle. This demonstrates both registering an API, and how to write
* Lua-facing functions.
* <p>
* This API is not available as a global (as {@link #getNames() returns nothing}), but is instead accessible via
* {@code require} (see {@link #getModuleName()}).
*
* <h2>Example</h2>
* <pre class="language language-lua">{@code
* local my_api = require("example.my_api")
* print("Turtle is facing " .. my_api.getDirection())
* }</pre>
*/
public class ExampleAPI implements ILuaAPI {
private final ITurtleAccess turtle;
public ExampleAPI(ITurtleAccess turtle) {
this.turtle = turtle;
}
public static void register() {
// @start region=register
ComputerCraftAPI.registerAPIFactory(computer -> {
// Read the turtle component.
var turtle = computer.getComponent(ComputerComponents.TURTLE);
// If present then add our API.
return turtle == null ? null : new ExampleAPI(turtle);
});
// @end region=register
}
@Override
public String[] getNames() {
return new String[0];
}
@Override
public @Nullable String getModuleName() {
return "example.my_api";
}
/**
* A Lua-facing function function that returns the direction the turtle is facing.
*
* @return The turtle's direction.
*/
@LuaFunction
public final String getDirection() {
return turtle.getDirection().getName();
}
/**
* A Lua-facing function using {@link Coerced}. Unlike a {@link LuaFunction} taking a raw {@link String}, this will
* accept any value, and convert it to a string.
*
* @param myString The value to write.
*/
// @start region=coerced
@LuaFunction
public final void writeString(Coerced<String> myString) {
String contents = myString.value();
System.out.println("Got " + contents);
}
// @end region=coerced
}

View File

@@ -0,0 +1,39 @@
package com.example.examplemod;
import com.example.examplemod.data.TurtleDataProvider;
import com.example.examplemod.peripheral.FurnacePeripheral;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
/**
* Our example mod, containing the various things we register.
* <p>
* This isn't an especially good template to follow! It's convenient for our example mod (as we need to be multi-loader
* compatible), but there's a good chance there's a better pattern to follow. For example, on Forge you'd use
* {@code DeferredRegister} to register things), and multi-loader mods probably have their own abstractions.
* <p>
* See {@code FabricExampleMod} and {@code ForgeExampleMod} for the actual mod entrypoints.
*/
public final class ExampleMod {
public static final String MOD_ID = "examplemod";
/**
* The upgrade serialiser for our example turtle upgrade. See the documentation for {@link TurtleUpgradeSerialiser}
* or {@code FabricExampleMod}/{@code ForgeExampleMod} for how this is registered.
* <p>
* This only defines the upgrade type. See {@link TurtleDataProvider} for defining the actual upgrade.
*/
// @start region=turtle_upgrades
public static final TurtleUpgradeSerialiser<ExampleTurtleUpgrade> EXAMPLE_TURTLE_UPGRADE = TurtleUpgradeSerialiser.simpleWithCustomItem(
ExampleTurtleUpgrade::new
);
// @end region=turtle_upgrades
public static void registerComputerCraft() {
// @start region=generic_source
ComputerCraftAPI.registerGenericSource(new FurnacePeripheral());
// @end region=generic_source
ExampleAPI.register();
}
}

View File

@@ -0,0 +1,17 @@
package com.example.examplemod;
import dan200.computercraft.api.turtle.AbstractTurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeType;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
/**
* An example turtle upgrade.
*/
// @start region=body
public class ExampleTurtleUpgrade extends AbstractTurtleUpgrade {
public ExampleTurtleUpgrade(ResourceLocation id, ItemStack stack) {
super(id, TurtleUpgradeType.PERIPHERAL, stack);
}
}
// @end region=body

View File

@@ -0,0 +1,17 @@
package com.example.examplemod.data;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DataProvider;
/**
* The entry point to example mod's data-generators.
* <p>
* This is called by our platform-specific entry-point (see {@code FabricExampleModDataGenerator} and
* {@code ForgeExampleModDataGenerator}. That said, the exact setup isn't relevant (it will vary depending on
* mod-loader), what's interesting is the contents of the {@link #run(DataGenerator.PackGenerator)} method!
*/
public final class ExampleModDataGenerators {
public static void run(DataGenerator.PackGenerator pack) {
pack.addProvider((DataProvider.Factory<?>) TurtleDataProvider::new);
}
}

View File

@@ -0,0 +1,34 @@
package com.example.examplemod.data;
import com.example.examplemod.ExampleMod;
import com.example.examplemod.ExampleTurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Items;
import java.util.function.Consumer;
/**
* A {@link TurtleUpgradeDataProvider} that generates the JSON for our {@linkplain ExampleTurtleUpgrade example
* upgrade}.
*
* @see ExampleModDataGenerators
*/
// @start region=body
public class TurtleDataProvider extends TurtleUpgradeDataProvider {
public TurtleDataProvider(PackOutput output) {
super(output);
}
@Override
protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
simpleWithCustomItem(
new ResourceLocation(ExampleMod.MOD_ID, "example_turtle_upgrade"),
ExampleMod.EXAMPLE_TURTLE_UPGRADE,
Items.COMPASS
).add(addUpgrade);
}
}
// @end region=body

View File

@@ -0,0 +1,12 @@
@ApiStatus.Internal
@DefaultQualifier(value = NonNull.class, locations = {
TypeUseLocation.RETURN,
TypeUseLocation.PARAMETER,
TypeUseLocation.FIELD,
})
package com.example.examplemod;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.checkerframework.framework.qual.TypeUseLocation;
import org.jetbrains.annotations.ApiStatus;

View File

@@ -0,0 +1,39 @@
package com.example.examplemod.peripheral;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.world.level.block.entity.BrewingStandBlockEntity;
import org.jetbrains.annotations.Nullable;
/**
* A peripheral that adds a {@code getFuel()} method to brewing stands. This demonstrates the usage of
* {@link IPeripheral}.
*
* @see dan200.computercraft.api.peripheral
* @see FurnacePeripheral Using {@code GenericPeripheral}.
*/
// @start region=body
public class BrewingStandPeripheral implements IPeripheral {
private final BrewingStandBlockEntity brewingStand;
public BrewingStandPeripheral(BrewingStandBlockEntity brewingStand) {
this.brewingStand = brewingStand;
}
@Override
public String getType() {
return "brewing_stand";
}
@LuaFunction
public final int getFuel() {
// Don't do it this way! Use an access widener/transformer to access the "fuel" field instead.
return brewingStand.saveWithoutMetadata().getInt("Fuel");
}
@Override
public boolean equals(@Nullable IPeripheral other) {
return other instanceof BrewingStandPeripheral o && brewingStand == o.brewingStand;
}
}
// @end region=body

View File

@@ -0,0 +1,44 @@
package com.example.examplemod.peripheral;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.AttachedComputerSet;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import org.jetbrains.annotations.Nullable;
/**
* A peripheral that tracks what computers it is attached to.
*
* @see AttachedComputerSet
*/
// @start region=body
public class ComputerTrackingPeripheral implements IPeripheral {
private final AttachedComputerSet computers = new AttachedComputerSet();
@Override
public void attach(IComputerAccess computer) {
computers.add(computer);
}
@Override
public void detach(IComputerAccess computer) {
computers.remove(computer);
}
@LuaFunction
public final void sayHello() {
// Queue a "hello" event on each computer.
computers.forEach(x -> x.queueEvent("hello", x.getAttachmentName()));
}
@Override
public String getType() {
return "my_peripheral";
}
@Override
public boolean equals(@Nullable IPeripheral other) {
return this == other;
}
}
// @end region=body

View File

@@ -0,0 +1,29 @@
package com.example.examplemod.peripheral;
import com.example.examplemod.ExampleMod;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
/**
* A peripheral that adds a {@code getBurnTime} method to furnaces. This is used to demonstrate the usage of
* {@link GenericPeripheral}.
*
* @see dan200.computercraft.api.peripheral
* @see BrewingStandPeripheral Using {@code IPeripheral}.
*/
// @start region=body
public class FurnacePeripheral implements GenericPeripheral {
@Override
public String id() {
return new ResourceLocation(ExampleMod.MOD_ID, "furnace").toString();
}
@LuaFunction(mainThread = true)
public int getBurnTime(AbstractFurnaceBlockEntity furnace) {
// Don't do it this way! Use an access widener/transformer to access the "litTime" field instead.
return furnace.saveWithoutMetadata().getInt("BurnTime");
}
}
// @end region=body

View File

@@ -0,0 +1,8 @@
{
"variants": {
"facing=east": {"model": "minecraft:block/lectern", "y": 90},
"facing=north": {"model": "minecraft:block/lectern", "y": 0},
"facing=south": {"model": "minecraft:block/lectern", "y": 180},
"facing=west": {"model": "minecraft:block/lectern", "y": 270}
}
}

View File

@@ -0,0 +1,8 @@
{
"variants": {
"facing=east": {"model": "computercraft:block/redstone_relay", "y": 90},
"facing=north": {"model": "computercraft:block/redstone_relay", "y": 0},
"facing=south": {"model": "computercraft:block/redstone_relay", "y": 180},
"facing=west": {"model": "computercraft:block/redstone_relay", "y": 270}
}
}

View File

@@ -17,6 +17,7 @@
"block.computercraft.monitor_advanced": "Advanced Monitor",
"block.computercraft.monitor_normal": "Monitor",
"block.computercraft.printer": "Printer",
"block.computercraft.redstone_relay": "Redstone Relay",
"block.computercraft.speaker": "Speaker",
"block.computercraft.turtle_advanced": "Advanced Turtle",
"block.computercraft.turtle_advanced.upgraded": "Advanced %s Turtle",
@@ -38,8 +39,6 @@
"commands.computercraft.generic.additional_rows": "%d additional rows…",
"commands.computercraft.generic.exception": "Unhandled exception (%s)",
"commands.computercraft.generic.no": "N",
"commands.computercraft.generic.no_position": "<no pos>",
"commands.computercraft.generic.position": "%s, %s, %s",
"commands.computercraft.generic.yes": "Y",
"commands.computercraft.help.desc": "Displays this help message",
"commands.computercraft.help.no_children": "%s has no sub-commands",
@@ -84,35 +83,35 @@
"gui.computercraft.config.disabled_generic_methods.tooltip": "A list of generic methods or method sources to disable. Generic methods are\nmethods added to a block/block entity when there is no explicit peripheral\nprovider. This includes inventory methods (i.e. inventory.getItemDetail,\ninventory.pushItems), and (if on Forge), the fluid_storage and energy_storage\nmethods.\nMethods in this list can either be a whole group of methods (computercraft:inventory)\nor a single method (computercraft:inventory#pushItems).\n",
"gui.computercraft.config.execution": "Execution",
"gui.computercraft.config.execution.computer_threads": "Computer threads",
"gui.computercraft.config.execution.computer_threads.tooltip": "Set the number of threads computers can run on. A higher number means more\ncomputers can run at once, but may induce lag. Please note that some mods may\nnot work with a thread count higher than 1. Use with caution.\nRange: > 1",
"gui.computercraft.config.execution.computer_threads.tooltip": "Set the number of threads computers can run on. A higher number means more\ncomputers can run at once, but may induce lag. Please note that some mods may\nnot work with a thread count higher than 1. Use with caution.",
"gui.computercraft.config.execution.max_main_computer_time": "Server tick computer time limit",
"gui.computercraft.config.execution.max_main_computer_time.tooltip": "The ideal maximum time a computer can execute for in a tick, in milliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time.\nRange: > 1",
"gui.computercraft.config.execution.max_main_computer_time.tooltip": "The ideal maximum time a computer can execute for in a tick, in milliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time.",
"gui.computercraft.config.execution.max_main_global_time": "Server tick global time limit",
"gui.computercraft.config.execution.max_main_global_time.tooltip": "The maximum time that can be spent executing tasks in a single tick, in\nmilliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time.\nRange: > 1",
"gui.computercraft.config.execution.max_main_global_time.tooltip": "The maximum time that can be spent executing tasks in a single tick, in\nmilliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time.",
"gui.computercraft.config.execution.tooltip": "Controls execution behaviour of computers. This is largely intended for\nfine-tuning servers, and generally shouldn't need to be touched.",
"gui.computercraft.config.floppy_space_limit": "Floppy Disk space limit (bytes)",
"gui.computercraft.config.floppy_space_limit.tooltip": "The disk space limit for floppy disks, in bytes.",
"gui.computercraft.config.http": "HTTP",
"gui.computercraft.config.http.bandwidth": "Bandwidth",
"gui.computercraft.config.http.bandwidth.global_download": "Global download limit",
"gui.computercraft.config.http.bandwidth.global_download.tooltip": "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s).\nRange: > 1",
"gui.computercraft.config.http.bandwidth.global_download.tooltip": "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s).",
"gui.computercraft.config.http.bandwidth.global_upload": "Global upload limit",
"gui.computercraft.config.http.bandwidth.global_upload.tooltip": "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s).\nRange: > 1",
"gui.computercraft.config.http.bandwidth.global_upload.tooltip": "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s).",
"gui.computercraft.config.http.bandwidth.tooltip": "Limits bandwidth used by computers.",
"gui.computercraft.config.http.enabled": "Enable the HTTP API",
"gui.computercraft.config.http.enabled.tooltip": "Enable the \"http\" API on Computers. Disabling this also disables the \"pastebin\" and\n\"wget\" programs, that many users rely on. It's recommended to leave this on and use\nthe \"rules\" config option to impose more fine-grained control.",
"gui.computercraft.config.http.max_requests": "Maximum concurrent requests",
"gui.computercraft.config.http.max_requests.tooltip": "The number of http requests a computer can make at one time. Additional requests\nwill be queued, and sent when the running requests have finished. Set to 0 for\nunlimited.\nRange: > 0",
"gui.computercraft.config.http.max_requests.tooltip": "The number of http requests a computer can make at one time. Additional requests\nwill be queued, and sent when the running requests have finished. Set to 0 for\nunlimited.",
"gui.computercraft.config.http.max_websockets": "Maximum concurrent websockets",
"gui.computercraft.config.http.max_websockets.tooltip": "The number of websockets a computer can have open at one time.\nRange: > 1",
"gui.computercraft.config.http.max_websockets.tooltip": "The number of websockets a computer can have open at one time.",
"gui.computercraft.config.http.proxy": "Proxy",
"gui.computercraft.config.http.proxy.host": "Host name",
"gui.computercraft.config.http.proxy.host.tooltip": "The hostname or IP address of the proxy server.",
"gui.computercraft.config.http.proxy.port": "Port",
"gui.computercraft.config.http.proxy.port.tooltip": "The port of the proxy server.\nRange: 1 ~ 65536",
"gui.computercraft.config.http.proxy.port.tooltip": "The port of the proxy server.",
"gui.computercraft.config.http.proxy.tooltip": "Tunnels HTTP and websocket requests through a proxy server. Only affects HTTP\nrules with \"use_proxy\" set to true (off by default).\nIf authentication is required for the proxy, create a \"computercraft-proxy.pw\"\nfile in the same directory as \"computercraft-server.toml\", containing the\nusername and password separated by a colon, e.g. \"myuser:mypassword\". For\nSOCKS4 proxies only the username is required.",
"gui.computercraft.config.http.proxy.type": "Proxy type",
"gui.computercraft.config.http.proxy.type.tooltip": "The type of proxy to use.\nAllowed Values: HTTP, HTTPS, SOCKS4, SOCKS5",
"gui.computercraft.config.http.proxy.type.tooltip": "The type of proxy to use.",
"gui.computercraft.config.http.rules": "Allow/deny rules",
"gui.computercraft.config.http.rules.tooltip": "A list of rules which control behaviour of the \"http\" API for specific domains or\nIPs. Each rule matches against a hostname and an optional port, and then sets several\nproperties for the request. Rules are evaluated in order, meaning earlier rules override\nlater ones.\n\nValid properties:\n - \"host\" (required): The domain or IP address this rule matches. This may be a domain name\n (\"pastebin.com\"), wildcard (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\").\n - \"port\" (optional): Only match requests for a specific port, such as 80 or 443.\n\n - \"action\" (optional): Whether to allow or deny this request.\n - \"max_download\" (optional): The maximum size (in bytes) that a computer can download in this\n request.\n - \"max_upload\" (optional): The maximum size (in bytes) that a computer can upload in a this request.\n - \"max_websocket_message\" (optional): The maximum size (in bytes) that a computer can send or\n receive in one websocket packet.\n - \"use_proxy\" (optional): Enable use of the HTTP/SOCKS proxy if it is configured.",
"gui.computercraft.config.http.tooltip": "Controls the HTTP API",
@@ -121,61 +120,61 @@
"gui.computercraft.config.log_computer_errors": "Log computer errors",
"gui.computercraft.config.log_computer_errors.tooltip": "Log exceptions thrown by peripherals and other Lua objects. This makes it easier\nfor mod authors to debug problems, but may result in log spam should people use\nbuggy methods.",
"gui.computercraft.config.maximum_open_files": "Maximum files open per computer",
"gui.computercraft.config.maximum_open_files.tooltip": "Set how many files a computer can have open at the same time. Set to 0 for unlimited.\nRange: > 0",
"gui.computercraft.config.maximum_open_files.tooltip": "Set how many files a computer can have open at the same time. Set to 0 for unlimited.",
"gui.computercraft.config.monitor_distance": "Monitor distance",
"gui.computercraft.config.monitor_distance.tooltip": "The maximum distance monitors will render at. This defaults to the standard tile\nentity limit, but may be extended if you wish to build larger monitors.\nRange: 16 ~ 1024",
"gui.computercraft.config.monitor_distance.tooltip": "The maximum distance monitors will render at. This defaults to the standard tile\nentity limit, but may be extended if you wish to build larger monitors.",
"gui.computercraft.config.monitor_renderer": "Monitor renderer",
"gui.computercraft.config.monitor_renderer.tooltip": "The renderer to use for monitors. Generally this should be kept at \"best\" - if\nmonitors have performance issues, you may wish to experiment with alternative\nrenderers.\nAllowed Values: BEST, TBO, VBO",
"gui.computercraft.config.monitor_renderer.tooltip": "The renderer to use for monitors. Generally this should be kept at \"best\" - if\nmonitors have performance issues, you may wish to experiment with alternative\nrenderers.",
"gui.computercraft.config.peripheral": "Peripherals",
"gui.computercraft.config.peripheral.command_block_enabled": "Enable command block peripheral",
"gui.computercraft.config.peripheral.command_block_enabled.tooltip": "Enable Command Block peripheral support",
"gui.computercraft.config.peripheral.max_notes_per_tick": "Maximum notes that a computer can play at once",
"gui.computercraft.config.peripheral.max_notes_per_tick.tooltip": "Maximum amount of notes a speaker can play at once.\nRange: > 1",
"gui.computercraft.config.peripheral.max_notes_per_tick.tooltip": "Maximum amount of notes a speaker can play at once.",
"gui.computercraft.config.peripheral.modem_high_altitude_range": "Modem range (high-altitude)",
"gui.computercraft.config.peripheral.modem_high_altitude_range.tooltip": "The range of Wireless Modems at maximum altitude in clear weather, in meters.\nRange: 0 ~ 100000",
"gui.computercraft.config.peripheral.modem_high_altitude_range.tooltip": "The range of Wireless Modems at maximum altitude in clear weather, in meters.",
"gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm": "Modem range (high-altitude, bad weather)",
"gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm.tooltip": "The range of Wireless Modems at maximum altitude in stormy weather, in meters.\nRange: 0 ~ 100000",
"gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm.tooltip": "The range of Wireless Modems at maximum altitude in stormy weather, in meters.",
"gui.computercraft.config.peripheral.modem_range": "Modem range (default)",
"gui.computercraft.config.peripheral.modem_range.tooltip": "The range of Wireless Modems at low altitude in clear weather, in meters.\nRange: 0 ~ 100000",
"gui.computercraft.config.peripheral.modem_range.tooltip": "The range of Wireless Modems at low altitude in clear weather, in meters.",
"gui.computercraft.config.peripheral.modem_range_during_storm": "Modem range (bad weather)",
"gui.computercraft.config.peripheral.modem_range_during_storm.tooltip": "The range of Wireless Modems at low altitude in stormy weather, in meters.\nRange: 0 ~ 100000",
"gui.computercraft.config.peripheral.modem_range_during_storm.tooltip": "The range of Wireless Modems at low altitude in stormy weather, in meters.",
"gui.computercraft.config.peripheral.monitor_bandwidth": "Monitor bandwidth",
"gui.computercraft.config.peripheral.monitor_bandwidth.tooltip": "The limit to how much monitor data can be sent *per tick*. Note:\n - Bandwidth is measured before compression, so the data sent to the client is\n smaller.\n - This ignores the number of players a packet is sent to. Updating a monitor for\n one player consumes the same bandwidth limit as sending to 20.\n - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40\n monitors to be updated in a single tick.\nSet to 0 to disable.\nRange: > 0",
"gui.computercraft.config.peripheral.monitor_bandwidth.tooltip": "The limit to how much monitor data can be sent *per tick*. Note:\n - Bandwidth is measured before compression, so the data sent to the client is\n smaller.\n - This ignores the number of players a packet is sent to. Updating a monitor for\n one player consumes the same bandwidth limit as sending to 20.\n - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40\n monitors to be updated in a single tick.\nSet to 0 to disable.",
"gui.computercraft.config.peripheral.tooltip": "Various options relating to peripherals.",
"gui.computercraft.config.term_sizes": "Terminal sizes",
"gui.computercraft.config.term_sizes.computer": "Computer",
"gui.computercraft.config.term_sizes.computer.height": "Terminal height",
"gui.computercraft.config.term_sizes.computer.height.tooltip": "Range: 1 ~ 255",
"gui.computercraft.config.term_sizes.computer.height.tooltip": "Height of computer terminal",
"gui.computercraft.config.term_sizes.computer.tooltip": "Terminal size of computers.",
"gui.computercraft.config.term_sizes.computer.width": "Terminal width",
"gui.computercraft.config.term_sizes.computer.width.tooltip": "Range: 1 ~ 255",
"gui.computercraft.config.term_sizes.computer.width.tooltip": "Width of computer terminal",
"gui.computercraft.config.term_sizes.monitor": "Monitor",
"gui.computercraft.config.term_sizes.monitor.height": "Max monitor height",
"gui.computercraft.config.term_sizes.monitor.height.tooltip": "Range: 1 ~ 32",
"gui.computercraft.config.term_sizes.monitor.height.tooltip": "Maximum height of monitors",
"gui.computercraft.config.term_sizes.monitor.tooltip": "Maximum size of monitors (in blocks).",
"gui.computercraft.config.term_sizes.monitor.width": "Max monitor width",
"gui.computercraft.config.term_sizes.monitor.width.tooltip": "Range: 1 ~ 32",
"gui.computercraft.config.term_sizes.monitor.width.tooltip": "Maximum width of monitors",
"gui.computercraft.config.term_sizes.pocket_computer": "Pocket Computer",
"gui.computercraft.config.term_sizes.pocket_computer.height": "Terminal height",
"gui.computercraft.config.term_sizes.pocket_computer.height.tooltip": "Range: 1 ~ 255",
"gui.computercraft.config.term_sizes.pocket_computer.height.tooltip": "Height of pocket computer terminal",
"gui.computercraft.config.term_sizes.pocket_computer.tooltip": "Terminal size of pocket computers.",
"gui.computercraft.config.term_sizes.pocket_computer.width": "Terminal width",
"gui.computercraft.config.term_sizes.pocket_computer.width.tooltip": "Range: 1 ~ 255",
"gui.computercraft.config.term_sizes.pocket_computer.width.tooltip": "Width of pocket computer terminal",
"gui.computercraft.config.term_sizes.tooltip": "Configure the size of various computer's terminals.\nLarger terminals require more bandwidth, so use with care.",
"gui.computercraft.config.turtle": "Turtles",
"gui.computercraft.config.turtle.advanced_fuel_limit": "Advanced Turtle fuel limit",
"gui.computercraft.config.turtle.advanced_fuel_limit.tooltip": "The fuel limit for Advanced Turtles.\nRange: > 0",
"gui.computercraft.config.turtle.advanced_fuel_limit.tooltip": "The fuel limit for Advanced Turtles.",
"gui.computercraft.config.turtle.can_push": "Turtles can push entities",
"gui.computercraft.config.turtle.can_push.tooltip": "If set to true, Turtles will push entities out of the way instead of stopping if\nthere is space to do so.",
"gui.computercraft.config.turtle.need_fuel": "Enable fuel",
"gui.computercraft.config.turtle.need_fuel.tooltip": "Set whether Turtles require fuel to move.",
"gui.computercraft.config.turtle.normal_fuel_limit": "Turtle fuel limit",
"gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "The fuel limit for Turtles.\nRange: > 0",
"gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "The fuel limit for Turtles.",
"gui.computercraft.config.turtle.tooltip": "Various options relating to turtles.",
"gui.computercraft.config.upload_max_size": "File upload size limit (bytes)",
"gui.computercraft.config.upload_max_size.tooltip": "The file upload size limit, in bytes. Must be in range of 1 KiB and 16 MiB.\nKeep in mind that uploads are processed in a single tick - large files or\npoor network performance can stall the networking thread. And mind the disk space!\nRange: 1024 ~ 16777216",
"gui.computercraft.config.upload_max_size.tooltip": "The file upload size limit, in bytes. Must be in range of 1 KiB and 16 MiB.\nKeep in mind that uploads are processed in a single tick - large files or\npoor network performance can stall the networking thread. And mind the disk space!",
"gui.computercraft.config.upload_nag_delay": "Upload nag delay",
"gui.computercraft.config.upload_nag_delay.tooltip": "The delay in seconds after which we'll notify about unhandled imports. Set to 0 to disable.\nRange: 0 ~ 60",
"gui.computercraft.config.upload_nag_delay.tooltip": "The delay in seconds after which we'll notify about unhandled imports. Set to 0 to disable.",
"gui.computercraft.pocket_computer_overlay": "Pocket computer open. Press ESC to close.",
"gui.computercraft.terminal": "Computer terminal",
"gui.computercraft.tooltip.computer_id": "Computer ID: %s",

View File

@@ -0,0 +1,9 @@
{
"parent": "minecraft:block/orientable_with_bottom",
"textures": {
"bottom": "computercraft:block/redstone_relay_bottom",
"front": "computercraft:block/redstone_relay_front",
"side": "computercraft:block/redstone_relay_side",
"top": "computercraft:block/redstone_relay_top"
}
}

View File

@@ -0,0 +1 @@
{"parent": "computercraft:block/redstone_relay"}

View File

@@ -1,6 +1,7 @@
{
"sources": [
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"},
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"}
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"},
{"type": "minecraft:single", "resource": "computercraft:entity/printout"}
]
}

View File

@@ -0,0 +1,16 @@
{
"parent": "minecraft:recipes/root",
"criteria": {
"has_cable": {
"conditions": {"items": [{"items": ["computercraft:wired_modem"]}]},
"trigger": "minecraft:inventory_changed"
},
"has_the_recipe": {
"conditions": {"recipe": "computercraft:redstone_relay"},
"trigger": "minecraft:recipe_unlocked"
}
},
"requirements": [["has_cable", "has_the_recipe"]],
"rewards": {"recipes": ["computercraft:redstone_relay"]},
"sends_telemetry_event": false
}

View File

@@ -0,0 +1,12 @@
{
"type": "minecraft:block",
"pools": [
{
"bonus_rolls": 0.0,
"conditions": [{"condition": "minecraft:survives_explosion"}],
"entries": [{"type": "minecraft:item", "name": "minecraft:lectern"}],
"rolls": 1.0
}
],
"random_sequence": "computercraft:blocks/lectern"
}

View File

@@ -0,0 +1,12 @@
{
"type": "minecraft:block",
"pools": [
{
"bonus_rolls": 0.0,
"conditions": [{"condition": "minecraft:survives_explosion"}],
"entries": [{"type": "minecraft:item", "name": "computercraft:redstone_relay"}],
"rolls": 1.0
}
],
"random_sequence": "computercraft:blocks/redstone_relay"
}

View File

@@ -15,7 +15,7 @@ import java.util.*;
* @param <T> The type of object that this registry provides details for.
*/
public class DetailRegistryImpl<T> implements DetailRegistry<T> {
private final Collection<DetailProvider<T>> providers = new ArrayList<>();
private final Collection<DetailProvider<? super T>> providers = new ArrayList<>();
private final DetailProvider<T> basic;
public DetailRegistryImpl(DetailProvider<T> basic) {
@@ -24,7 +24,7 @@ public class DetailRegistryImpl<T> implements DetailRegistry<T> {
}
@Override
public synchronized void addProvider(DetailProvider<T> provider) {
public synchronized void addProvider(DetailProvider<? super T> provider) {
Objects.requireNonNull(provider, "provider cannot be null");
if (!providers.contains(provider)) providers.add(provider);
}

View File

@@ -23,7 +23,6 @@ import dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType;
import dan200.computercraft.shared.common.ClearColourRecipe;
import dan200.computercraft.shared.common.ColourableRecipe;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.common.HeldItemMenu;
import dan200.computercraft.shared.computer.apis.CommandAPI;
import dan200.computercraft.shared.computer.blocks.CommandComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
@@ -41,6 +40,9 @@ import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.details.BlockDetails;
import dan200.computercraft.shared.details.ItemDetails;
import dan200.computercraft.shared.integration.PermissionRegistry;
import dan200.computercraft.shared.lectern.CustomLecternBlock;
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
import dan200.computercraft.shared.media.PrintoutMenu;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.media.items.RecordMedia;
@@ -49,7 +51,6 @@ import dan200.computercraft.shared.media.recipes.DiskRecipe;
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.network.container.HeldItemContainerData;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
@@ -61,6 +62,8 @@ import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
import dan200.computercraft.shared.peripheral.printer.PrinterBlock;
import dan200.computercraft.shared.peripheral.printer.PrinterBlockEntity;
import dan200.computercraft.shared.peripheral.printer.PrinterMenu;
import dan200.computercraft.shared.peripheral.redstone.RedstoneRelayBlock;
import dan200.computercraft.shared.peripheral.redstone.RedstoneRelayBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlock;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
import dan200.computercraft.shared.platform.PlatformHelper;
@@ -101,10 +104,12 @@ import net.minecraft.world.item.crafting.CustomRecipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import net.minecraft.world.level.material.MapColor;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
@@ -129,7 +134,7 @@ public final class ModRegistry {
return BlockBehaviour.Properties.of().strength(2);
}
private static BlockBehaviour.Properties computerProperties() {
private static BlockBehaviour.Properties redstoneConductor() {
// Computers shouldn't conduct redstone through them, so set isRedstoneConductor to false. This still allows
// redstone to connect to computers though as it's a signal source.
return properties().isRedstoneConductor((block, level, blockPos) -> false);
@@ -144,11 +149,11 @@ public final class ModRegistry {
}
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = REGISTRY.register("computer_normal",
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), BlockEntities.COMPUTER_NORMAL));
() -> new ComputerBlock<>(redstoneConductor().mapColor(MapColor.STONE), BlockEntities.COMPUTER_NORMAL));
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = REGISTRY.register("computer_advanced",
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), BlockEntities.COMPUTER_ADVANCED));
() -> new ComputerBlock<>(redstoneConductor().mapColor(MapColor.GOLD), BlockEntities.COMPUTER_ADVANCED));
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command",
() -> new CommandComputerBlock<>(computerProperties().strength(-1, 6000000.0F), BlockEntities.COMPUTER_COMMAND));
() -> new CommandComputerBlock<>(redstoneConductor().strength(-1, 6000000.0F), BlockEntities.COMPUTER_COMMAND));
public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = REGISTRY.register("turtle_normal",
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), BlockEntities.TURTLE_NORMAL));
@@ -172,6 +177,13 @@ public final class ModRegistry {
public static final RegistryEntry<WiredModemFullBlock> WIRED_MODEM_FULL = REGISTRY.register("wired_modem_full",
() -> new WiredModemFullBlock(modemProperties().mapColor(MapColor.STONE)));
public static final RegistryEntry<CableBlock> CABLE = REGISTRY.register("cable", () -> new CableBlock(modemProperties().mapColor(MapColor.STONE)));
public static final RegistryEntry<CustomLecternBlock> LECTERN = REGISTRY.register("lectern", () -> new CustomLecternBlock(
BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.5F).sound(SoundType.WOOD).ignitedByLava()
));
public static final RegistryEntry<RedstoneRelayBlock> REDSTONE_RELAY = REGISTRY.register("redstone_relay",
() -> new RedstoneRelayBlock(redstoneConductor().mapColor(MapColor.STONE)));
}
public static class BlockEntities {
@@ -213,6 +225,10 @@ public final class ModRegistry {
ofBlock(Blocks.WIRELESS_MODEM_NORMAL, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_NORMAL.get(), p, s, false));
public static final RegistryEntry<BlockEntityType<WirelessModemBlockEntity>> WIRELESS_MODEM_ADVANCED =
ofBlock(Blocks.WIRELESS_MODEM_ADVANCED, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_ADVANCED.get(), p, s, true));
public static final RegistryEntry<BlockEntityType<CustomLecternBlockEntity>> LECTERN = ofBlock(Blocks.LECTERN, CustomLecternBlockEntity::new);
public static final RegistryEntry<BlockEntityType<RedstoneRelayBlockEntity>> REDSTONE_RELAY = ofBlock(Blocks.REDSTONE_RELAY, RedstoneRelayBlockEntity::new);
}
public static final class Items {
@@ -258,6 +274,7 @@ public final class ModRegistry {
public static final RegistryEntry<BlockItem> WIRELESS_MODEM_NORMAL = ofBlock(Blocks.WIRELESS_MODEM_NORMAL, BlockItem::new);
public static final RegistryEntry<BlockItem> WIRELESS_MODEM_ADVANCED = ofBlock(Blocks.WIRELESS_MODEM_ADVANCED, BlockItem::new);
public static final RegistryEntry<BlockItem> WIRED_MODEM_FULL = ofBlock(Blocks.WIRED_MODEM_FULL, BlockItem::new);
public static final RegistryEntry<BlockItem> REDSTONE_RELAY = ofBlock(Blocks.REDSTONE_RELAY, BlockItem::new);
public static final RegistryEntry<CableBlockItem.Cable> CABLE = REGISTRY.register("cable",
() -> new CableBlockItem.Cable(Blocks.CABLE.get(), properties()));
@@ -309,11 +326,8 @@ public final class ModRegistry {
public static final RegistryEntry<MenuType<PrinterMenu>> PRINTER = REGISTRY.register("printer",
() -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
() -> ContainerData.toType(
HeldItemContainerData::new,
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand())
));
public static final RegistryEntry<MenuType<PrintoutMenu>> PRINTOUT = REGISTRY.register("printout",
() -> new MenuType<>((i, c) -> PrintoutMenu.createRemote(i), FeatureFlags.VANILLA_SET));
}
static class ArgumentTypes {
@@ -409,6 +423,7 @@ public final class ModRegistry {
out.accept(Items.CABLE.get());
out.accept(Items.WIRED_MODEM.get());
out.accept(Items.WIRED_MODEM_FULL.get());
out.accept(Items.REDSTONE_RELAY.get());
out.accept(Items.MONITOR_NORMAL.get());
out.accept(Items.MONITOR_ADVANCED.get());

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