1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-16 14:37:39 +00:00

Compare commits

...

124 Commits

Author SHA1 Message Date
Jonathan Coates
2a04fb71fd Bump CC:T to 1.107.0 2023-08-13 08:34:45 +01:00
Srendi
df61389304 Fix some typos in CONTRIBUTING.md (#1549) 2023-08-05 21:30:19 +00:00
Jonathan Coates
b6632c9ed9 Fix model hook nullability
Only an issue on the latest FAPI, so we also bump that.
2023-08-05 10:32:21 +01:00
Jonathan Coates
41b6711b38 Change button text in render rather than an override
This made more sense on 1.19.2 and before, but now that we have to do
this for tooltips, we might as well do it for messages as well.

Closes #1538, though hopefully will be resolved on the VO side too.
2023-07-27 19:12:27 +01:00
Virtio
02f0b7ec14 Fix typo in shell docs (#1537) 2023-07-25 16:27:23 +00:00
Jonathan Coates
a9d31cd3c6 Re-run datagen
- Remove some unused translation keys.
 - Run tools/language.py to sort the current translations and remove the
   aforementioned unused keys.
 - Update turtle tool impostor recipes - these now include the tool NBT!
2023-07-23 22:22:48 +01:00
Jonathan Coates
43744b0e85 Clean up ApiWrapper a little
We now wrap every Api with an ApiWrapper, rather than making it pretend
to be an ILuaApi.
2023-07-23 22:22:22 +01:00
Weblate
55510c42db Translations for Czech
Translations for Polish

Translations for French

Translations for Spanish

Translations for German

Co-authored-by: Patriik <apatriik0@gmail.com>
Co-authored-by: Sammy <SammyKoch@pm.me>
Co-authored-by: SquidDev <git@squiddev.cc>
2023-07-23 21:22:10 +00:00
Jonathan Coates
57a944fd90 Render enchanted upgrades with a glint (#1532) 2023-07-23 10:18:22 +00:00
Jonathan Coates
940f59b116 Automatically bake models from turtle modellers (#1531)
This avoids us having to list a bunch of model locations twice, and
hopefully makes things a little cleaner for add-on authors too
2023-07-22 19:51:50 +00:00
Jonathan Coates
dd08d1ec8e Fix client tests not running on Forge
This was introduced by the FG6 update - the cctest.client property
didn't get set, and so tests never started!
2023-07-22 19:40:50 +01:00
Jonathan Coates
90ed0b24e7 Send block updates to the computer when updating redstone
Fixes #1520
2023-07-22 13:50:13 +01:00
Jonathan Coates
0ad399a528 Rewrite turtle.dig tool actions
- Move the tool action before the "is block present" check, fixes
   #1527. This is where it was before, but we flipped it around in the
   tool rewrite.

 - Don't reuse as much turtle.place logic for tool actions. This fixes
   some instances where tools could till/level dirt through solid
   blocks.
2023-07-21 08:58:02 +01:00
Jonathan Coates
1b88213eca Make our registry wrapper implement IdMap
This allows us to use some of the byte-buffer helper methods directly
with our registries, rather than rolling our own helpers.
2023-07-19 20:20:38 +01:00
Erlend
eef05b9854 Add dark-mode compatible logo to README (#1521) 2023-07-18 19:26:45 +00:00
Jonathan Coates
24d74f5c80 Update to latest Fabric
- Overhaul model loading to work with the new API. This allows for
   using the emissive texture system in a more generic way, which is
   nice!

 - Convert some of our custom models to use Fabric's model hooks (i.e.
   emitItemQuads). We don't make use of this right now, but might be
   useful for rendering tools with enchantment glints.

   Note this does /not/ change any of the turtle block entity rendering
   code to use Fabric/Forge's model code. This will be a change we want
   to make in the future.

 - Some cleanup of our config API. This fixes us printing lots of
   warnings when creating a new config file on Fabric (same bug also
   occurs on Forge, but that's a loader problem).

 - Fix a few warnings
2023-07-18 19:27:27 +01:00
Jonathan Coates
c2988366d8 Add EMI compatibility
This just adds stack comparisons, so that upgrades are considered (much
like JEI and REI). No dynamic recipes, as EMI doesn't support those :(.
2023-07-10 20:31:06 +01:00
Jonathan Coates
ec0765ead1 Bump Cobalt to 0.7.1
- Fix numbers ending in "f" or "d" being lexed.
 - Fix string.pack's "z" causing out-of-bounds errors.
2023-07-10 20:17:54 +01:00
Weblate
b94e34f372 Translations for Italian
Co-authored-by: Alessandro <ale.proto00@gmail.com>
2023-07-10 15:03:03 +00:00
Jonathan Coates
af3263dec2 Allow disabling generic methods
Suddenly so easy as of the refactoring in 910a63214e395ecae6993d8e0487384c725b3dd3!

Closes #1382
2023-07-09 19:59:08 +01:00
Jonathan Coates
7f25c9a66b Only load client config on the client 2023-07-09 18:44:33 +01:00
Jonathan Coates
9ca3efff3c Add data generator support for required mods
We've supported resource conditions in the upgrade JSON for an age, but
don't expose it in our data generators at all.

Indeed, using these hooks is a bit of a pain to do in multi-loader
setups, as the JSON is different between the two loaders. We could
generate the JSON for all loaders at once, but it feels nicer to use
the per-loader APIs to add the conditions.

For now, we just support generating a single condition - whether a mod
is loaded not, via the requireMod(...) method.
2023-07-09 14:09:44 +01:00
Jonathan Coates
aaf8c248a8 Bump CC:T to 1.106.1 2023-07-08 09:37:43 +01:00
Jonathan Coates
df26cd267a Fix conflicts with other mods replacing reach distance 2023-07-08 09:35:06 +01:00
Jonathan Coates
8914b78816 Also block the CGNAT range (100.64.0.0/10) 2023-07-08 09:27:09 +01:00
Jonathan Coates
9ea7f45fa7 Fix class cast exception with ObjectSource
We were generating methods with the original object, rather than the
extra one.

Updated our tests to actually catch this. Unfortunately the only places
we use this interface is in HTTP responses and transferred files,
neither of which show up in the Lua-side tests.
2023-07-07 18:05:02 +01:00
Jonathan Coates
d351bc33c6 Bump CC:T to 1.106.0 2023-07-07 00:16:48 +01:00
Jonathan Coates
5d71770931 Several command permission fixes
- Attach permission checks to the first argument (so the literal
   command name) rather than the last argument. This fixes commands
   showing up when they shouldn't.

 - HelpingArgumentBuilder now inherits permissions of its leaf nodes.
   This only really impacts the "track" subcommand.

 - Don't autocomplete the computer selector for the "queue" subcommand.
   As everyone has permission for this command, it's possible to find
   all computer ids and labels in the world.

   I'm in mixed minds about this, but don't think this is an exploit -
   computer ids/labels are sent to in-range players so shouldn't be
   considered secret - but worth patching none-the-less.
2023-07-06 23:16:22 +01:00
Jonathan Coates
4bbde8c50c Tighten up the $private HTTP rule
- Block multicast and the fd00::/8 address ranges.
 - Block several cloud metadata providers which sit outside the standard
   address ranges.
2023-07-06 23:16:22 +01:00
Jonathan Coates
cc8c1f38e7 Small documentation improvements
- Document that settings.set doesn't persist values. I think this
   closes #1512 - haven't heard back from them.

 - Add missing close reasons to the websocket_closed event. Closes #1493.

 - Mention what values are preserved by os.queueEvent. This is just the
   same as modem.transmit. Closes #1490.
2023-07-06 23:03:22 +01:00
Jonathan Coates
cab9c9772a Add some tests for new turtle tool functionality
- Normalise upgrade keys, to be "allowEnchantments" and
   "consumeDurability". We were previously inconsistent with
   allow/allows and consumes.
 - Add tests for durability and enchantments of pickaxes.
 - Fix a couple of issues with the original upgrade NBT being modified.
 - Now store the item's tag under a separate key rather than on the
   root. This makes syncing the NBT between the two much nicer.
2023-07-06 22:28:56 +01:00
Jonathan Coates
e337a63712 Bump FG and Loom
Also split up CI steps a little, so that it's more clear what failed.
2023-07-05 20:58:15 +01:00
Weblate
efa92b741b Translations for Russian (ru_ru)
Translations for French

Co-authored-by: chesiren <chesiren63@gmail.com>
Co-authored-by: ego-rick <vsevidcev@gmail.com>
2023-07-04 23:03:03 +00:00
Jonathan Coates
a91ac6f214 Make turtle tools a little more flexible
Turtle tools now accept two additional JSON fields

 - allowEnchantments: Whether items with enchantments (or any
   non-standard NBT) can be equipped.
 - consumesDurability: Whether durability will be consumed. This can be
   "never" (the current and default behaviour), "always", and
   "when_enchanted".

Closes #1501.
2023-07-03 22:14:31 +01:00
PenguinEncounter
943a9406b1 Fix turtle.refuel example (#1510) 2023-07-02 17:19:09 +00:00
Jonathan Coates
0b2bb5e7b5 Use exclusiveContent for our maven
This is a little nasty as we need to include ForgeGradle's repo too, but
should still help a bit!
2023-07-02 12:21:03 +01:00
Jonathan Coates
8708048b6e Try to make turtle_test.peripheral_change more robust
Replace the arbitrary sleep with a thenWaitUntil.
2023-07-02 11:46:03 +01:00
Jonathan Coates
d138d9c4a5 Preserve item NBT for turtle tools
This is a pre-requisite for #1501, and some other refactorings I want to do.

Also fix items in the turtle upgrade slots vanishing. We now explicitly
invalidate the cache when setting the item.
2023-07-02 11:02:10 +01:00
Edvin
f54cb8a432 Allow upgrades to read/write upgrade data from ItemStacks (#1465) 2023-07-02 10:55:55 +01:00
Jonathan Coates
94f5ede75a Remove superflous Nonnull/Notnull annotations 2023-07-01 19:32:28 +01:00
Jonathan Coates
1977556da4 Deprecate IPocketAccess.getUpgrades
I think this left over from CCTweaks or Peripheral++. It doesn't really
make sense as an API - if/when we add multiple upgrades, we'll want a
different API for this.
2023-07-01 18:34:17 +01:00
Jonathan Coates
9eabb29999 Move the model cache inside TurtleModelParts
This removes a tiny bit of duplication (at the cost of mode code), but
makes the interface more intuitive, as there's no bouncing between
getCombination -> cache -> buildModel.
2023-07-01 18:27:36 +01:00
Jonathan Coates
ecf880ed82 Document HTTP rules a little better
It turns out we don't document the "port" option anywhere, so probably
worth doing a bit of an overhaul here.

 - Expand the top-level HTTP rules comment, clarifying how things are
   matched and describing each field.

 - Improve the comments on the default HTTP rule. We now also describe
   the $private rule and its motivation.

 - Don't drop/ignore invalid rules. This gets written back to the
   original config file, so is very annoying! Instead we now log an
   error and convert the rule into a "deny all" rule, which should make
   it obvious something is wrong.
2023-07-01 16:16:06 +01:00
Jonathan Coates
655d5aeca8 Improve REPL's handling of expressions
- Remove the "force_print" code. This is a relic of before we used
   table.pack, and so didn't know how many expressions had been
   returned.

 - Check the input string is a valid expression separately before
   wrapping it in an _echo(...). Fixes #1506.
2023-07-01 12:37:48 +01:00
Jonathan Coates
34f41c4039 Be lazier in configuring Forge runs 2023-06-29 22:31:49 +01:00
Jonathan Coates
f5b16261cc Update to Gradle 8.x
- Update to Loom 1.2 and FG 6.0. ForgeGradle has changed how it
   generates the runXyz tasks, which makes running our tests much
   harder. I've raised an issue upstream, but for now we do some nasty
   poking of internals.

 - Fix Sodium/Iris tests. Loom 1.1 changed how remapped configurations
   are generated - we create a dummy source set and associate the
   remapped configuration with that. All nasty stuff.

 - Publish the common library. I'm not a fan of this, but given how much
   internals I'm poking elsewhere, should probably get off my high
   horse.

 - Add renderdoc support to the client gametests, enabled with
   -Prenderdoc.
2023-06-29 20:10:17 +01:00
Jonathan Coates
7eb3b691da Fix misplaced calls to IArguments.escapes
- Fix mainThread=true methods calling IArguments.escapes too late. This
   should be done before scheduling on the main thread, not on the main
   thread itself!

 - Fix VarargsArguments.escapes not checking that the argument haven't
   been closed. This is slightly prone to race conditions, but I don't
   think it's worth the overhead of tracking the owning thread.

   Maybe when panama and its resource scopes are released.

Thanks Sara for pointing this out!

Slightly irked that none of our tests caught this. Alas.

Also fix a typo in AddressPredicate. Yes, no commit discipline.
2023-06-27 18:28:54 +01:00
Jonathan Coates
910a63214e Make Generic methods per-ComputerContext
- Move the class cache out of Generator into MethodSupplierImpl. This
   means we cache class generation globally (that's really expensive!),
   but the class -> method list lookup is local.

 - Move the global GenericSource/GenericMethod registry out of core,
   passing in the list of generic methods to the ComputerContext.

I'm not entirely thrilled by the slight overlap of MethodSupplierImpl and
Generator here, something to clean up in the future.
2023-06-26 21:46:55 +01:00
Jonathan Coates
591a7eca23 Clean up how we enumerate Lua/peripheral methods
- Move several interfaces out of `d00.computercraft.core.asm` into a
   new `aethods` package. It may make sense to expose this to the
   public API in a future commit (possibly part of #1462).

 - Add a new MethodSupplier<T> interface, which provides methods to
   iterate over all methods exported by an object (either directly, or
   including those from ObjectSources).

   This interface's concrete implementation (asm.MethodSupplierImpl),
   uses Generators and IntCaches as before - we can now make that all
   package-private though, which is nice!

 - Make the LuaMethod and PeripheralMethod MethodSupplier local to the
   ComputerContext. This currently has no effect (the underlying
   Generator is still global), but eventually we'll make GenericMethods
   non-global, which unlocks the door for #1382.

 - Update everything to use this new interface. This is mostly pretty
   sensible, but is a little uglier on the MC side (especially in
   generic peripherals), as we need to access the global ServerContext.
2023-06-26 19:42:42 +01:00
Jonathan Coates
a29a516a3f Small refactoring to generic peripherals
- Remove SidedGenericPeripheral (we never used this!), adding the
   functionality to GenericPeripheral directly. This is just used on the
   Fabric side for now, but might make sense with Forge too.

 - Move GenericPeripheralBuilder into the common project - this is
   identical between the two projects!

 - GenericPeripheralBuilder now generates a list of methods internally,
   rather than being passed the methods.

 - Add a tiny bit of documentation.
2023-06-26 19:11:59 +01:00
Jonathan Coates
4a5e03c11a Convert NamedMethod into a record 2023-06-26 18:51:14 +01:00
Jonathan Coates
50d460624f Make the list of API factories per-ComputerContext
The registry is still a singleton inside the Minecraft code, but this
makes the core a little cleaner.
2023-06-26 18:48:26 +01:00
Jonathan Coates
bc500df921 Use a builder for constructing ComputerContexts
We've got a few optional fields here, and more on their way, so this
ends up being a little nicer.
2023-06-26 18:42:19 +01:00
Jonathan Coates
4accda6b8e Some tiny optimisations to the window API
- Use integer indexes instead of strings (i.e. text, textColour). This
   is a tiny bit faster.
 - Avoid re-creating tables when clearing.

We're still mostly limited by the VM (slow) and string concatenation
(slow!). Short of having some low-level mutable buffer type, I don't
think we can improve this much :(.
2023-06-25 21:04:05 +01:00
Jonathan Coates
54ab98473f Be lazy in reporting errors in the lexer
Instead of reporting an error with `.report(f(...))`, we now do
`.report(f, ...)`. This allows consumers to ignore error messages when
not needed, such as when just doing syntax highlighting.
2023-06-25 15:48:57 +01:00
Jonathan Coates
7ffdbb2316 Publish docs for 1.20 as well 2023-06-25 09:47:56 +01:00
Jonathan Coates
672c2cf029 Limit turtle's reach distance in Item.use
When a turtle attempts to place a block, it does so by searching for
nearby blocks and attempting to place the item against that block.

This has slightly strange behaviour when working with "placable"
non-block items though (such as buckets or boats). In this case, we call
Item.use, which doesn't take in the position of the block we're placing
against. Instead these items do their own ray trace, using the default
reach distance.

If the block we're trying to place against is non-solid, the ray trace
will go straight through it and continue (up to the maximum of 5
blocks), allowing placing the item much further away.

Our fix here is to override the default reach distance of our fake
players, limiting it to 2. This is easy on Forge (it has built-in
support), and requires a mixin on Fabric.

Closes #1497.
2023-06-24 17:09:34 +01:00
Jonathan Coates
c3bdb0440e Merge pull request #1494 from Wojbie/lua.lua-require-fix
Update lua.lua require logic.
2023-06-20 23:38:22 +01:00
Wojbie
88f0c44152 Update lua.lua require logic.
This makes it more consistent in situations when someone requires with path starting with /.
2023-06-20 23:33:53 +02:00
JackMacWindows
c8523bf479 Add ability to serialize Unicode strings to JSON (#1489) 2023-06-18 21:42:28 +00:00
Jonathan Coates
953372b1b7 Fix quad order when rendering turtles upside down
- Reverse quads in our model transformer and when rendering as a block
   entity.
 - Correctly recompute normals when the quads have been inverted.

Closes #1283
2023-06-18 19:32:23 +01:00
Jonathan Coates
36b9f4ec55 Merge pull request #1485 from cc-tweaked/feature/turtle-upgrade-ui
Allow changing turtle upgrades from the GUI
2023-06-18 08:09:47 +01:00
Jonathan Coates
ccfed0059b Render the computer cursor as emissive
- Split the front face of the computer model into two layers - one for
   the main texture, and one for the cursor. This is actually a
   simplification of what we had before, which is nice.

 - Make the cursor layer render as an emissive quad, meaning it glows in
   the dark. This is very easy on Forge (just some model JSON) and very
   hard on Fabric (requires a custom model loader).
2023-06-17 18:02:05 +01:00
Jonathan Coates
7b4ba11fb4 Allow changing turtle upgrades from the GUI
This adds two slots to the right of the turtle interface which contain
the left and right upgrades of a turtle.

 - Add turtle_upgrade_{left,right} indicators, which used as the
   background texture for the two upgrade slots. In order to use
   Slot.getNoItemIcon, we need to bake these into the block texture
   atlas.

   This is done with the new atlas JSON and a data generator - it's
   mostly pretty simple, but we do now need a client-side data
   generator, which is a little ugly to do.

 - Add a new UpgradeContainer/UpgradeSlot, which exposes a turtle's
   upgrades in an inventory-like way.

 - Update the turtle menu and screen to handle these new slots.
2023-06-17 10:48:44 +01:00
Jonathan Coates
8ccd5a560c Add support for codecs to our data generator system
This already exists in both upstream loaders, we just need to abstract
over it.
2023-06-17 10:37:19 +01:00
Weblate
0f866836a0 Translations for Russian (ru_ru)
Co-authored-by: Andrew71 <andrey.nikitin.vladimirovich@gmail.com>
2023-06-16 21:03:01 +00:00
Jonathan Coates
df591cd7c6 Allow extending UpgradeDataProvider
Closes #1477
2023-06-15 18:57:25 +01:00
Jonathan Coates
c7f3d4f45d Port fs.find to CraftOS
Also add support for "?" style wildcards.

Closes #1455.
2023-06-15 18:57:05 +01:00
Jonathan Coates
77ac04cb7a Fix changelog notes
Also exclude data generator cache from the Forge jar. Didn't have any
better place to put this 😳.
2023-06-15 18:32:30 +01:00
Jonathan Coates
201df7e987 Merge pull request #1481 from znepb/fix-flute-code
Fix missing curly brace in instrument list
2023-06-13 14:24:27 +01:00
Marcus
5722e51735 Fix missing curly brace in instrument list 2023-06-13 06:04:35 -04:00
Jonathan Coates
7a291619ab Merge pull request #1480 from MCJack123/patch-16
Fixed typo in cc.image.nft docs
2023-06-13 07:26:59 +01:00
JackMacWindows
4b9b19b02d Fixed typo in cc.image.nft docs 2023-06-12 19:49:45 -04:00
Jonathan Coates
ec52f3e0e8 Bump CC:T to 1.104.0 2023-06-10 08:55:07 +01:00
Jonathan Coates
96847bb8c2 Make turtle placing consistent at all positions
Turtles used to place stairs upside-down when at y<0. Now we know why!
2023-06-08 20:52:32 +01:00
Jonathan Coates
68ef9f717b Deprecate itemGroups field
Since 1.19.3, this was only populated when the player opened the
creative menu, and so was useless in survival or multi-player
worlds.

Rather than removing the field entirely (🦑 backwards compatibility), we
replace it with the empty list. We also remove it from the docs, and add
a note explaining what the field used to do.

Closes #1285, albeit in the least satisfactory way possible.
2023-06-08 20:33:31 +01:00
Jonathan Coates
cba207d62d Use Minecraft's method for checking paste events
Fixes #1473.

There's an argument we should use Screen.hasControlDown() (which handles
Cmd vs Ctrl) instead of checking the modifiers, but we then need to
update all the translation strings, and I'm not convinced it's worth it
right now.
2023-06-08 18:48:50 +01:00
Jonathan Coates
e157978afc Deprecate ITurtleAccess.getVisual{Position,Yaw} 2023-06-08 18:32:00 +01:00
Jonathan Coates
ef19988c37 Add translations for HTTP proxy config 2023-06-07 18:33:26 +01:00
Drew Edwards
c91bb5ac33 Add support for proxying HTTP requests (#1461) 2023-06-06 18:58:24 +00:00
Commandcracker
d45f2bfe80 Fix channel names in colors documentation (#1467) 2023-06-04 12:10:04 +00:00
Jonathan Coates
8fdef542c9 Update to latest JEI 2023-06-04 12:49:46 +01:00
Jonathan Coates
6e7cbf25e8 Clean up turtle/pocket computer item creation
- Remove ITurtleItem (and ITurtleBlockEntity): this was, AFAIK, mostly
   a relic of the pre-1.13 code where we had multiple turtle items.

   I do like the theory of abstracting everything out behind an
   interface, but given there's only one concrete implementation, I'm
   not convinced it's worth it right now.

 - Remove TurtleItemFactory/PocketComputerItemFactory: we now prefer
   calling the instance .create(...) method where we have the item
   available (for instance upgrade recipes).

   In the cases we don't (creating an item the first time round), we now
   move the static .create(...) method to the actual item class.
2023-06-04 11:25:30 +01:00
Jonathan Coates
b691430889 Clean up our thread creation code a little
- Provide a helper method for creating threads with a lower priority.

 - Use that in our network code (which already used this priority) and
   for the computer worker threads (which used the default priority
   before). I genuinely thought I did this years ago.
2023-06-03 20:51:21 +01:00
Jonathan Coates
f0abb83f6e Eagerly create upgrade registries for Fabric
Instead of creating the upgrade serialiser registries in mod
initialisation, we now do it when the API is created. This ensures the
registries are available for other mods, irrespective of mod load order.

This feels a little sad (we're doing side effects in the static
initialiser), but is /fine/ - it's pretty much what other mods do.
2023-06-03 19:04:02 +01:00
Jonathan Coates
4d064d1552 Document some of our client classes
This is mostly aiming to give an overview rather than be anything
comprehensive (there's another 230+ undocumented classes to go :p), but
it's a start.

Mostly just an excuse for me to procrastinate working on the nasty bugs
though!
2023-06-02 21:59:45 +01:00
Jonathan Coates
12ca8583f4 Add a couple of tests for HTTP 2023-06-02 20:57:45 +01:00
Jonathan Coates
9cca908bff Better logging in the term code 2023-06-01 20:32:04 +01:00
Jonathan Coates
5082b1677c Move MainThread config to its own class
This means the config is no longer stored as static fields, which is a
little cleaner. Would like to move everything else in the future, but
this is a good first step.
2023-05-31 22:21:17 +01:00
Jonathan Coates
590ef86c93 Show an error message when editing read-only files
See #1222
2023-05-31 19:32:35 +01:00
Jonathan Coates
b09200a270 Fix breaking computers having no particle or sound 2023-05-31 19:18:19 +01:00
Jonathan Coates
d3b39be9a8 Fix enums not being added to config files
The default validator allows for null (why???), and so accepts it not
being present at all.
2023-05-30 22:20:47 +01:00
Jonathan Coates
e153839a98 Detect playing HTML files in speaker
Goes someway towards preventing people playing YouTube or non-raw GitHub
files.
2023-05-26 10:19:42 +01:00
Jonathan Coates
842eb67e17 Normalise line endings in the SanitisedError test
We could use the system line ending when printing the message, but this
is fine too.
2023-05-25 09:49:40 +01:00
Jonathan Coates
ebed357f05 Truncate longer error messages
We could do this in a more concise manner by wrapping Throwable rather
than reimplementing printStackTrace. However, doing this way allows us
to handle nested exceptions too.
2023-05-25 09:10:03 +01:00
Jonathan Coates
9d16fda266 Fix image links in Modrinth description
Modrinth proxies images hosted on non-trusted domains through wsrv.nl,
for understandable reasons. However, wsrv.nl blocks tweaked.cc - I'm not
sure why. Instead we reference the image on GH directly, which works!

Also:
 - Fix the modrinthSyncBody task pointing to a missing file.
 - Update the licenses of a few files, post getting permission from
   people. <3 all.
2023-05-24 22:35:45 +01:00
Jonathan Coates
2ae14b4c08 Add support for HTTP timeouts (#1453)
- Add a `timeout` parameter to http request and websocket methods.
    - For requests, this sets the connection and read timeout.
    - For websockets, this sets the connection and handshake timeout.
 - Remove the timeout config option, as this is now specified by user
   code.
 - Use netty for handling websocket handshakes, meaning we no longer
   need to deal with pongs.
2023-05-23 22:32:16 +00:00
Jonathan Coates
55ed0dc3ef Use UTC formats for the 12am os.date tests
We could alternatively use os.time { ... } here, but this feels more
"correct"?

Fixes #1447
2023-05-21 11:08:01 +01:00
Jonathan Coates
3112f455ae Support arguments being coerced from strings
In this case, we use Lua's tostring(x) semantics (well, modulo
metamethods), instead of Java's Object.toString(x) call. This ensures
that values are formatted (mostly) consistently between Lua and Java
methods.

 - Add IArguments.getStringCoerced, which uses Lua's tostring semantics.

 - Add a Coerced<T> wrapper type, which says to use the .getXCoerced
   methods. I'm not thrilled about this interface - there's definitely
   an argument for using annotations - but this is probably more
   consistent for now.

 - Convert existing methods to use this call.

Closes #1445
2023-05-20 18:54:22 +01:00
Jonathan Coates
e0216f8792 Some core cleanup
- Move some ArgumentHelpers methods to the core project.
 - Remove static imports in CobaltLuaMachine, avoiding confusing calls
   to valueOf.
2023-05-18 19:20:27 +01:00
Jonathan Coates
03c794cd53 Remove packet distance restriction for wired modems
Given that the network (and peripheral access within the network) no
longer have distance limits, packets being limited makes less sense.

Closes #1434.
2023-05-18 19:01:09 +01:00
Jonathan Coates
b91bca8830 Merge pull request #1446 from Erb3/erb3-path-3
Add `colors.fromBlit`.
2023-05-17 22:47:28 +01:00
Jonathan Coates
90177c9f8b Convert to block comments
And remove empty line between block and definition
2023-05-17 22:21:38 +01:00
Erlend
952674a161 Improve argument validation in colors.fromBlit 2023-05-17 20:20:53 +02:00
Erlend
25ab7d0dcb Improve test for colors.fromBlit to show inverse 2023-05-17 20:16:47 +02:00
Erlend
98ee37719d Improve documentation for colors.fromBlit and colors.toBlit 2023-05-17 20:09:59 +02:00
khankul
e24b5f0888 Make maximum upload file size configurable (#1417) 2023-05-17 13:07:16 +00:00
Erlend
090d17c528 Add tests for colors.fromBlit 2023-05-17 14:43:08 +02:00
Jonathan Coates
470f2b098d Force LF for .kts files too 2023-05-17 13:28:03 +01:00
Erlend
722b870f98 fix #1367: Add colors.fromBlit 2023-05-17 14:02:21 +02:00
Jonathan Coates
3bf29695fb Fix a couple of wee bugs
- Fix monitor renderer debug text showing up even when debug overlay
   was not visible. This was a Forge-specific bug, which is why I'd not
   noticed it I guess??

 - Don't crash on alternative implementations of LoggerContext. Fixes
   #1431. I'm not 100% sure what is causing this - it doesn't happen
   with just CC:T at least - but at least we can bodge around it.
2023-05-07 10:15:03 +01:00
Weblate
99e8cd29a1 Translations for Ukrainian
Co-authored-by: Edvin <siredvin.dark@gmail.com>
2023-05-07 06:02:55 +00:00
Jonathan Coates
0e48ac1dfe Speed up JSON string parsing
We now use Lua patterns to find runs of characters. This makes string
parsing 3-4x faster. Ish - I've not run any exact benchmarks.

Closes #1408
2023-05-04 19:47:52 +01:00
Jonathan Coates
232c051526 Update to latest Fabric
- Use Fabric FakePlayer class
 - Remove redundant explosion accessor
2023-05-04 18:50:00 +01:00
Jonathan Coates
34928257c6 Merge pull request #1430 from Lupus590/patch-1
Fix typo in docs
2023-05-04 14:39:16 +01:00
Lupus590
5eb4a9033b Fix typo in docs
foreground and background *characters* -> foreground and background *colours*
2023-05-04 13:58:56 +01:00
Jonathan Coates
54b7366b2d Merge pull request #1421 from zyxkad/patch-1
Fixed `turtle.detectUp` & `turtle.detectDown` doc
2023-04-23 17:20:44 +01:00
Kevin Z
d6751b8e3d Update TurtleAPI.java
Fixed `turtle.detectUp` & `turtle.detectDown` doc
2023-04-23 09:58:29 -06:00
Jonathan Coates
5d7cbc8c64 Use Fabric's new SlottedStorage for inventory methods
This is a little more general than InventoryStorage and means we can get
rid of our nasty double chest hack.

The generic peripheral system doesn't currently support generics (hah),
and so we need to use a wrapper class for now.
2023-04-16 09:16:39 +01:00
Jonathan Coates
e8fd460935 Fix arrow keys not working in the printout UI
This is a Fabric-specific bug - vanilla always returns true from
keyPressed, but Forge patches it to not do that. Closes #1409.
2023-04-16 09:15:51 +01:00
Jonathan Coates
a0efe637d6 Merge pull request #1414 from Erb3/mc-1.19.x
Fix on/off translation being inverted
2023-04-16 09:13:47 +01:00
Erlend
f099b21f6f fix: on/off translation inverted 2023-04-15 16:21:53 +02:00
Jonathan Coates
3920ff08ab Some README cleanup
- Standardise our badges a little, adding a modrinth badge.
 - Mention Fabric and Forge support.
 - Don't include MC version in the Modrinth version number. I feel this
   was required at some point, but apparently not any more! This also
   allows us to use Modrinth for the Forge update JSON.
2023-04-06 18:18:40 +01:00
Jonathan Coates
ccd7f6326a Allow GPS hosts to be closer together
I'm not quite sure why I typed a 5 here. There we go.
2023-04-05 21:59:20 +01:00
391 changed files with 8006 additions and 3225 deletions

1
.gitattributes vendored
View File

@@ -11,6 +11,7 @@ projects/common/src/testMod/resources/data/cctest/structures/* linguist-generate
*.gradle eol=lf diff=java
*.java eol=lf diff=java
*.kt eol=lf diff=java
*.kts eol=lf diff=java
*.lua eol=lf
*.md eol=lf diff=markdown
*.txt eol=lf

View File

@@ -11,6 +11,7 @@ body:
- 1.16.x
- 1.18.x
- 1.19.x
- 1.20.x
validations:
required: true
- type: input

View File

@@ -8,16 +8,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone repository
- name: 📥 Clone repository
uses: actions/checkout@v3
- name: Set up Java
- name: 📥 Set up Java
uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'temurin'
- name: Setup Gradle
- name: 📥 Setup Gradle
uses: gradle/gradle-build-action@v2
with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
@@ -27,39 +27,45 @@ jobs:
mkdir -p ~/.gradle
echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
- name: Build with Gradle
run: |
./gradlew assemble || ./gradlew assemble
./gradlew downloadAssets || ./gradlew downloadAssets
./gradlew build
- name: ⚒️ Build
run: ./gradlew assemble || ./gradlew assemble
- name: Run client tests
- 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: Prepare Jars
- name: 🧪 Parse test reports
run: ./tools/parse-reports.py
if: ${{ failure() }}
- name: 📦 Prepare Jars
run: |
# Find the main jar and append the git hash onto it.
mkdir -p jars
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
- name: Upload Jar
- name: 📤 Upload Jar
uses: actions/upload-artifact@v3
with:
name: CC-Tweaked
path: ./jars
- name: Upload coverage
- name: 📤 Upload coverage
uses: codecov/codecov-action@v3
- name: Parse test reports
run: ./tools/parse-reports.py
if: ${{ failure() }}
- name: Run linters
uses: pre-commit/action@v3.0.0
build-core:
strategy:
fail-fast: false

View File

@@ -4,6 +4,7 @@ on:
push:
branches:
- mc-1.19.x
- mc-1.20.x
jobs:
make_doc:

View File

@@ -6,6 +6,7 @@ Upstream-Contact: Jonathan Coates <git@squiddev.cc>
Files:
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/fabric/src/generated/*
projects/forge/src/generated/*
@@ -16,6 +17,7 @@ Copyright: The CC: Tweaked Developers
License: CC0-1.0
Files:
doc/images/*
package.json
package-lock.json
projects/common/src/client/resources/computercraft-client.mixins.json
@@ -24,6 +26,10 @@ Files:
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
@@ -41,18 +47,14 @@ Copyright: The CC: Tweaked Developers
License: MPL-2.0
Files:
doc/images/*
doc/logo.png
doc/logo-darkmode.png
projects/common/src/main/resources/assets/computercraft/models/*
projects/common/src/main/resources/assets/computercraft/textures/*
projects/common/src/main/resources/pack.mcmeta
projects/common/src/main/resources/pack.png
projects/core/src/main/resources/data/computercraft/lua/rom/autorun/.ignoreme
projects/core/src/main/resources/data/computercraft/lua/rom/help/*
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/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/levels/*
projects/web/src/export/items/computercraft/*
Comment: Bulk-license original assets as CCPL.

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: MPL-2.0
# Contributing to CC: Tweaked
As with many open source projects, CC: Tweaked thrives on contributions from other people! This document (hopefully)
provides an introduction as to how to get started in helping out.
provides an introduction as to how to get started with helping out.
If you've any other questions, [just ask the community][community] or [open an issue][new-issue].
@@ -28,7 +28,7 @@ automatically with GitHub, so please don't submit PRs adding/changing translatio
## Setting up a development environment
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
- Make sure you've got the following software instealled:
- Make sure you've got the following software installed:
- Java Development Kit (JDK) installed. This can be downloaded from [Adoptium].
- [Git](https://git-scm.com/).
- If you want to work on documentation, [NodeJS][node].
@@ -51,10 +51,10 @@ If you want to run CC:T in a normal Minecraft instance, run `./gradlew assemble`
## 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]!
looking to make your changes. As always, if you're not sure, [do ask the community][community]!
### Testing
When making larger changes, it's may be useful to write a test to make sure your code works as expected.
When making larger changes, it may be useful to write a test to make sure your code works as expected.
CC: Tweaked has several test suites, each designed to test something different:
@@ -91,11 +91,11 @@ file.
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific
markdown features - if you can, do check what the documentation looks like locally!
markdown features. If you can, do check what the documentation looks like locally!
When writing long-form documentation (such as the guides in [doc/guides](doc/guides)), I find it useful to tell a
narrative. Think of what you want the user to learn or achieve, then start introducing a simple concept and then talk
about how you can build on that, until you've covered everything!
narrative. Think of what you want the user to learn or achieve, then start introducing a simple concept, and then talk
about how you can build on that until you've covered everything!
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
[community]: README.md#community "Get in touch with the community."

View File

@@ -4,14 +4,21 @@ SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
# ![CC: Tweaked](doc/logo.png)
[![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](http://cf.way2muchnoise.eu/title/cc-tweaked.svg)][CurseForge]
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./doc/logo-darkmode.png">
<source media="(prefers-color-scheme: light)" srcset="./doc/logo.png">
<img alt="CC: Tweaked" src="./doc/logo.png">
</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 better performance, stability, and a wealth of new features.
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 requires the [Minecraft Forge][forge] mod loader, but
[versions are available for Fabric][ccrestitched].
CC: Tweaked can be installed from [CurseForge] or [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
@@ -42,7 +49,7 @@ repositories {
dependencies {
// Vanilla (i.e. for multi-loader systems)
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api")
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
// Forge Gradle
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
@@ -55,6 +62,19 @@ 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, file an issue, and we can look into
exposing more features.
@@ -65,8 +85,8 @@ 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"
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[ccrestitched]: https://www.curseforge.com/minecraft/mc-mods/cc-restitched "Download CC: Restitched from CurseForge"
[Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[Fabric]: https://fabricmc.net/use/installer/ "Download Fabric."
[forum]: https://forums.computercraft.cc/
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"

View File

@@ -10,8 +10,9 @@ import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("cc-tweaked.java-convention")
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")
}

View File

@@ -37,22 +37,37 @@ java {
repositories {
mavenCentral()
maven("https://squiddev.cc/maven") {
val mainMaven = maven("https://squiddev.cc/maven") {
name = "SquidDev"
content {
includeGroup("org.squiddev")
includeGroup("cc.tweaked")
// Things we mirror
includeGroup("dev.architectury")
includeGroup("maven.modrinth")
includeGroup("me.shedaniel")
includeGroup("me.shedaniel.cloth")
includeGroup("mezz.jei")
includeModule("com.terraformersmc", "modmenu")
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
includeModule("org.spongepowered", "mixin")
}
}
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")
includeModule("org.squiddev", "Cobalt")
// Things we mirror
includeGroup("dev.architectury")
includeGroup("dev.emi")
includeGroup("maven.modrinth")
includeGroup("me.shedaniel.cloth")
includeGroup("me.shedaniel")
includeGroup("mezz.jei")
includeModule("com.terraformersmc", "modmenu")
}
}
}
dependencies {
@@ -104,6 +119,7 @@ tasks.withType(JavaCompile::class.java).configureEach {
tasks.processResources {
exclude("**/*.license")
exclude(".cache")
}
tasks.withType(AbstractArchiveTask::class.java).configureEach {

View File

@@ -33,7 +33,6 @@ val publishCurseForge by tasks.registering(TaskPublishCurseForge::class) {
enabled = apiToken != ""
val mainFile = upload("282001", modPublishing.output.get().archiveFile)
dependsOn(modPublishing.output) // See https://github.com/Darkhax/CurseForgeGradle/pull/7.
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"
@@ -46,14 +45,14 @@ tasks.publish { dependsOn(publishCurseForge) }
modrinth {
token.set(findProperty("modrinthApiKey") as String? ?: "")
projectId.set("gu7yAYhd")
versionNumber.set("$mcVersion-$modVersion")
versionNumber.set(modVersion)
versionName.set(modVersion)
versionType.set(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).")
syncBodyFrom.set(provider { file("doc/mod-page.md").readText() })
syncBodyFrom.set(provider { rootProject.file("doc/mod-page.md").readText() })
}
tasks.publish { dependsOn(tasks.modrinth) }

View File

@@ -2,8 +2,6 @@
//
// SPDX-License-Identifier: MPL-2.0
import org.gradle.kotlin.dsl.`maven-publish`
plugins {
`java-library`
`maven-publish`

View File

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

@@ -60,7 +60,7 @@ class IlluaminatePlugin : Plugin<Project> {
/** Define a dependency for illuaminate from a version number and the current operating system. */
private fun illuaminateArtifact(project: Project, version: String): Dependency {
val osName = System.getProperty("os.name").toLowerCase()
val osName = System.getProperty("os.name").lowercase()
val (os, suffix) = when {
osName.contains("windows") -> Pair("windows", ".exe")
osName.contains("mac os") || osName.contains("darwin") -> Pair("macos", "")
@@ -68,7 +68,7 @@ class IlluaminatePlugin : Plugin<Project> {
else -> error("Unsupported OS $osName for illuaminate")
}
val osArch = System.getProperty("os.arch").toLowerCase()
val osArch = System.getProperty("os.arch").lowercase()
val arch = when {
// On macOS the x86_64 binary will work for both ARM and Intel Macs through Rosetta.
os == "macos" -> "x86_64"

View File

@@ -4,6 +4,7 @@
package cc.tweaked.gradle
import net.minecraftforge.gradle.common.util.RunConfig
import org.gradle.api.GradleException
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.invocation.Gradle
@@ -32,11 +33,14 @@ abstract class ClientJavaExec : JavaExec() {
usesService(clientRunner)
}
@get:Input
val renderdoc get() = project.hasProperty("renderdoc")
/**
* When [false], tests will not be run automatically, allowing the user to debug rendering.
*/
@get:Input
val clientDebug get() = project.hasProperty("clientDebug")
val clientDebug get() = renderdoc || project.hasProperty("clientDebug")
/**
* When [false], tests will not run under a framebuffer.
@@ -50,6 +54,25 @@ abstract class ClientJavaExec : JavaExec() {
@get:OutputFile
val testResults = project.layout.buildDirectory.file("test-results/$name.xml")
private fun setTestProperties() {
if (!clientDebug) systemProperty("cctest.client", "")
if (renderdoc) environment("LD_PRELOAD", "/usr/lib/librenderdoc.so")
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
workingDir(project.buildDir.resolve("gametest").resolve(name))
}
init {
setTestProperties()
}
/**
* Set this task to run a given [RunConfig].
*/
fun setRunConfig(config: RunConfig) {
(this as JavaExec).setRunConfig(config)
setTestProperties() // setRunConfig may clobber some properties, ensure everything is set.
}
/**
* Copy configuration from a task with the given name.
*/
@@ -61,10 +84,7 @@ abstract class ClientJavaExec : JavaExec() {
fun copyFrom(task: JavaExec) {
for (dep in task.dependsOn) dependsOn(dep)
task.copyToFull(this)
if (!clientDebug) systemProperty("cctest.client", "")
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
workingDir(project.buildDir.resolve("gametest").resolve(name))
setTestProperties() // copyToFull may clobber some properties, ensure everything is set.
}
/**

View File

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

@@ -13,6 +13,15 @@ The @{websocket_closed} event is fired when an open WebSocket connection is clos
## Return Values
1. @{string}: The event name.
2. @{string}: The URL of the WebSocket that was closed.
3. <span class="type">@{string}|@{nil}</span>: The [server-provided reason][close_reason]
the websocket was closed. This will be @{nil} if the connection was closed
abnormally.
4. <span class="type">@{number}|@{nil}</span>: The [connection close code][close_code],
indicating why the socket was closed. This will be @{nil} if the connection
was closed abnormally.
[close_reason]: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.6 "The WebSocket Connection Close Reason, RFC 6455"
[close_code]: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5 "The WebSocket Connection Close Code, RFC 6455"
## Example
Prints a message when a WebSocket is closed (this may take a minute):

View File

@@ -6,10 +6,10 @@ SPDX-License-Identifier: MPL-2.0
# ![CC: Tweaked](logo.png)
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 better performance, stability, and a wealth of new features.
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 requires the [Minecraft Forge][forge] mod loader, but
[versions are available for Fabric][ccrestitched].
CC: Tweaked can be installed from [CurseForge] or [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
@@ -54,7 +54,8 @@ CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please
[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."
[ccrestitched]: https://www.curseforge.com/minecraft/mc-mods/cc-restitched "Download CC: Restitched from CurseForge"
[Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[Fabric]: https://fabricmc.net/use/installer/ "Download Fabric."
[lua]: https://www.lua.org/ "Lua's main website"
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"

BIN
doc/logo-darkmode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -5,7 +5,7 @@ SPDX-License-Identifier: MPL-2.0
-->
# ![CC: Tweaked](https://tweaked.cc/logo.png)
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 better performance, stability, and a wealth of new features.
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.
## Testimonials
@@ -22,13 +22,13 @@ CC: Tweaked is a mod for Minecraft which adds programmable computers, turtles an
Controlled using the [Lua programming language][lua], CC: Tweaked's computers provides all the tools you need to start
writing code and automating your Minecraft world.
![A ComputerCraft terminal open and ready to be programmed.](https://tweaked.cc/images/basic-terminal.png)
![A ComputerCraft terminal open and ready to be programmed.](https://raw.githubusercontent.com/cc-tweaked/CC-Tweaked/HEAD/doc/images/basic-terminal.png)
While computers are incredibly powerful, they're rather limited by their inability to move about. *Turtles* are the
solution here. They can move about the world, placing and breaking blocks, swinging a sword to protect you from zombies,
or whatever else you program them to!
![A turtle tunneling in Minecraft.](https://tweaked.cc/images/turtle.png)
![A turtle tunneling in Minecraft.](https://raw.githubusercontent.com/cc-tweaked/CC-Tweaked/HEAD/doc/images/turtle.png)
Not all problems can be solved with a pickaxe though, and so CC: Tweaked also provides a bunch of additional peripherals
for your computers. You can play a tune with speakers, display text or images on a monitor, connect all your
@@ -37,7 +37,7 @@ computers together with modems, and much more.
Computers can now also interact with inventories such as chests, allowing you to build complex inventory and item
management systems.
![A chest's contents being read by a computer and displayed on a monitor.](https://tweaked.cc/images/peripherals.png)
![A chest's contents being read by a computer and displayed on a monitor.](https://raw.githubusercontent.com/cc-tweaked/CC-Tweaked/HEAD/doc/images/peripherals.png)
## Getting Started
While ComputerCraft is lovely for both experienced programmers and for people who have never coded before, it can be a
@@ -58,8 +58,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"
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[ccrestitched]: https://modrinth.com/mod/cc-restitched "Download CC: Restitched from Modrinth"
[lua]: https://www.lua.org/ "Lua's main website"
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
[IRC]: http://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"

View File

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

View File

@@ -7,20 +7,20 @@
# Minecraft
# MC version is specified in gradle.properties, as we need that in settings.gradle.
# Remember to update corresponding versions in fabric.mod.json/mods.toml
fabric-api = "0.75.3+1.19.4"
fabric-loader = "0.14.17"
fabric-api = "0.86.1+1.19.4"
fabric-loader = "0.14.21"
forge = "45.0.42"
forgeSpi = "6.0.0"
mixin = "0.8.5"
parchment = "2023.03.12"
parchmentMc = "1.19.3"
parchment = "2023.06.26"
parchmentMc = "1.19.4"
# Normal dependencies
asm = "9.3"
autoService = "1.0.1"
checkerFramework = "3.32.0"
cobalt = "0.7.0"
cobalt-next = "0.7.1" # Not a real version, used to constrain the version we accept.
cobalt = "0.7.1"
cobalt-next = "0.7.2" # Not a real version, used to constrain the version we accept.
fastutil = "8.5.9"
guava = "31.1-jre"
jetbrainsAnnotations = "24.0.1"
@@ -33,8 +33,9 @@ nightConfig = "3.6.5"
slf4j = "1.7.36"
# Minecraft mods
emi = "1.0.8+1.19.4"
iris = "1.5.2+1.19.4"
jei = "11.3.0.262"
jei = "13.1.0.11"
modmenu = "6.1.0-rc.1"
oculus = "1.2.5"
rei = "10.0.578"
@@ -48,21 +49,21 @@ jqwik = "1.7.2"
junit = "5.9.2"
# Build tools
cctJavadoc = "1.6.1"
cctJavadoc = "1.7.0"
checkstyle = "10.3.4"
curseForgeGradle = "1.0.11"
curseForgeGradle = "1.0.14"
errorProne-core = "2.18.0"
errorProne-plugin = "3.0.1"
fabric-loom = "1.1.10"
forgeGradle = "5.1.+"
fabric-loom = "1.3.7"
forgeGradle = "6.0.8"
githubRelease = "2.2.12"
ideaExt = "1.1.6"
illuaminate = "0.1.0-24-gdb28902"
illuaminate = "0.1.0-28-ga7efd71"
librarian = "1.+"
minotaur = "2.+"
mixinGradle = "0.7.+"
nullAway = "0.9.9"
quiltflower = "1.8.0"
quiltflower = "1.10.0"
spotless = "6.17.0"
taskTree = "2.1.1"
vanillaGradle = "0.2.1-SNAPSHOT"
@@ -83,6 +84,8 @@ kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core",
kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
netty-http = { module = "io.netty:netty-codec-http", version.ref = "netty" }
netty-socks = { module = "io.netty:netty-codec-socks", version.ref = "netty" }
netty-proxy = { module = "io.netty:netty-handler-proxy", 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" }
@@ -90,10 +93,11 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
# Minecraft mods
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
jei-api = { module = "mezz.jei:jei-1.19.2-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.19.2-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.19.2-forge", version.ref = "jei" }
jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.19.4-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.19.4-forge", version.ref = "jei" }
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
@@ -148,10 +152,10 @@ kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
# Minecraft
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["oculus", "jei-api"]
externalMods-forge-runtime = []
externalMods-forge-runtime = ["jei-forge"]
externalMods-fabric = ["nightConfig-core", "nightConfig-toml"]
externalMods-fabric-compile = ["iris", "jei-api", "rei-api", "rei-builtin"]
externalMods-fabric-runtime = ["modmenu"]
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
# Testing
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]

Binary file not shown.

View File

@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

19
gradlew vendored
View File

@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +80,10 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -143,12 +140,16 @@ fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,6 +194,10 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in

1
gradlew.bat vendored
View File

@@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

View File

@@ -9,7 +9,11 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
/**
* The public API for client-only code.
*
* @see dan200.computercraft.api.ComputerCraftAPI The main API
*/
public final class ComputerCraftAPIClient {
private ComputerCraftAPIClient() {
}

View File

@@ -11,9 +11,13 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
/**
* Provides models for a {@link ITurtleUpgrade}.
@@ -28,15 +32,45 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
* When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
*
* @param upgrade The upgrade that you're getting the model for.
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models!
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models, unless
* {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overriden.
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade.
*/
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
/**
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
* crafting item}.
* Obtain the model to be used when rendering a turtle peripheral.
* <p>
* This is used when rendering the turtle's item model, and so no {@link ITurtleAccess} is available.
*
* @param upgrade The upgrade that you're getting the model for.
* @param data Upgrade data instance for current turtle side.
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade.
*/
default TransformedModel getModel(T upgrade, CompoundTag data, TurtleSide side) {
return getModel(upgrade, (ITurtleAccess) null, side);
}
/**
* Get a list of models that this turtle modeller depends on.
* <p>
* Models included in this list will be loaded and baked alongside item and block models, and so may be referenced
* by {@link TransformedModel#of(ResourceLocation)}. You do not need to override this method if you will load models
* by other means.
*
* @return A list of models that this modeller depends on.
* @see UnbakedModel#getDependencies()
*/
default Collection<ResourceLocation> getDependencies() {
return List.of();
}
/**
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(CompoundTag)}
* upgrade item}.
* <p>
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
* model type. It will not appear correct for 3D models with additional depth, such as blocks.
@@ -46,7 +80,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
*/
@SuppressWarnings("unchecked")
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.FLAT_ITEM;
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.UPGRADE_ITEM;
}
/**
@@ -58,7 +92,8 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
* @return The constructed modeller.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
// TODO(1.21.0): Remove this.
return sided((ResourceLocation) left, right);
}
/**
@@ -70,6 +105,16 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
* @return The constructed modeller.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
return new TurtleUpgradeModeller<>() {
@Override
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
}
@Override
public Collection<ResourceLocation> getDependencies() {
return List.of(left, right);
}
};
}
}

View File

@@ -6,13 +6,20 @@ package dan200.computercraft.api.client.turtle;
import com.mojang.math.Transformation;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4f;
import javax.annotation.Nullable;
class TurtleUpgradeModellers {
private static final Transformation leftTransform = getMatrixFor(-0.40625f);
private static final Transformation rightTransform = getMatrixFor(0.40625f);
private static final Transformation leftTransform = getMatrixFor(-0.4065f);
private static final Transformation rightTransform = getMatrixFor(0.4065f);
private static Transformation getMatrixFor(float offset) {
var matrix = new Matrix4f();
@@ -26,6 +33,23 @@ class TurtleUpgradeModellers {
return new Transformation(matrix);
}
static final TurtleUpgradeModeller<ITurtleUpgrade> FLAT_ITEM = (upgrade, turtle, side) ->
TransformedModel.of(upgrade.getCraftingItem(), side == TurtleSide.LEFT ? leftTransform : rightTransform);
static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller();
private static class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
return getModel(turtle == null ? upgrade.getCraftingItem() : upgrade.getUpgradeItem(turtle.getUpgradeNBTData(side)), side);
}
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
return getModel(upgrade.getUpgradeItem(data), side);
}
private TransformedModel getModel(ItemStack stack, TurtleSide side) {
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);
}
}
}

View File

@@ -5,6 +5,7 @@
package dan200.computercraft.impl.client;
import dan200.computercraft.impl.Services;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
@@ -24,6 +25,15 @@ public interface ClientPlatformHelper {
*/
BakedModel getModel(ModelManager manager, ResourceLocation location);
/**
* Wrap this model in a version which renders a foil/enchantment glint.
*
* @param model The model to wrap.
* @return The wrapped model.
* @see RenderType#glint()
*/
BakedModel createdFoiledModel(BakedModel model);
static ClientPlatformHelper get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;

View File

@@ -5,9 +5,11 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.Map;
@@ -69,6 +71,8 @@ public interface IPocketAccess {
*
* @return The upgrade's NBT.
* @see #updateUpgradeNBTData()
* @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see UpgradeBase#getUpgradeData(ItemStack)
*/
CompoundTag getUpgradeNBTData();
@@ -80,7 +84,10 @@ public interface IPocketAccess {
void updateUpgradeNBTData();
/**
* Remove the current peripheral and create a new one. You may wish to do this if the methods available change.
* Remove the current peripheral and create a new one.
* <p>
* You may wish to do this if the methods available change, for instance when the {@linkplain #getEntity() owning
* entity} changes.
*/
void invalidatePeripheral();
@@ -88,6 +95,8 @@ public interface IPocketAccess {
* Get a list of all upgrades for the pocket computer.
*
* @return A collection of all upgrade names.
* @deprecated This is a relic of a previous API, which no longer makes sense with newer versions of ComputerCraft.
*/
@Deprecated(forRemoval = true)
Map<ResourceLocation, IPeripheral> getUpgrades();
}

View File

@@ -21,6 +21,6 @@ import java.util.function.Consumer;
*/
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>> {
public PocketUpgradeDataProvider(PackOutput output) {
super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.REGISTRY_ID);
super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.registryId());
}
}

View File

@@ -7,6 +7,7 @@ package dan200.computercraft.api.pocket;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
@@ -31,9 +32,21 @@ import java.util.function.Function;
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
*
* @deprecated Use {@link #registryId()} instead.
*/
@Deprecated(forRemoval = true)
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
/**
* The ID for the associated registry.
*
* @return The registry key.
*/
static ResourceKey<Registry<PocketUpgradeSerialiser<?>>> registryId() {
return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
}
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.

View File

@@ -8,10 +8,13 @@ import com.mojang.authlib.GameProfile;
import dan200.computercraft.api.lua.ILuaCallback;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.ApiStatus;
@@ -75,7 +78,9 @@ public interface ITurtleAccess {
* @param f The subframe fraction.
* @return A vector containing the floating point co-ordinates at which the turtle resides.
* @see #getVisualYaw(float)
* @deprecated Will be removed in 1.20.
*/
@Deprecated(forRemoval = true)
Vec3 getVisualPosition(float f);
/**
@@ -84,7 +89,9 @@ public interface ITurtleAccess {
* @param f The subframe fraction.
* @return The yaw the turtle is facing.
* @see #getVisualPosition(float)
* @deprecated Will be removed in 1.20.
*/
@Deprecated(forRemoval = true)
float getVisualYaw(float f);
/**
@@ -241,23 +248,51 @@ public interface ITurtleAccess {
void playAnimation(TurtleAnimation animation);
/**
* Returns the turtle on the specified side of the turtle, if there is one.
* Returns the upgrade on the specified side of the turtle, if there is one.
*
* @param side The side to get the upgrade from.
* @return The upgrade on the specified side of the turtle, if there is one.
* @see #setUpgrade(TurtleSide, ITurtleUpgrade)
* @see #getUpgradeWithData(TurtleSide)
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
*/
@Nullable
ITurtleUpgrade getUpgrade(TurtleSide side);
/**
* Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeNBTData(TurtleSide)
* update data}.
*
* @param side The side to get the upgrade from.
* @return The upgrade on the specified side of the turtle, along with its upgrade data, if there is one.
* @see #getUpgradeWithData(TurtleSide)
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
*/
default @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side) {
var upgrade = getUpgrade(side);
return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData(side));
}
/**
* Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
*
* @param side The side to set the upgrade on.
* @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgrade(TurtleSide)
* @deprecated Use {@link #setUpgradeWithData(TurtleSide, UpgradeData)}
*/
void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade);
@Deprecated
default void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
setUpgradeWithData(side, upgrade == null ? null : UpgradeData.ofDefault(upgrade));
}
/**
* Set the upgrade for a given side and its upgrade data.
*
* @param side The side to set the upgrade on.
* @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgradeWithData(TurtleSide)
*/
void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
/**
* Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
@@ -277,6 +312,8 @@ public interface ITurtleAccess {
* @param side The side to get the upgrade data for.
* @return The upgrade-specific data.
* @see #updateUpgradeNBTData(TurtleSide)
* @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see UpgradeBase#getUpgradeData(ItemStack)
*/
CompoundTag getUpgradeNBTData(TurtleSide side);

View File

@@ -7,6 +7,7 @@ package dan200.computercraft.api.turtle;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import javax.annotation.Nullable;
@@ -79,4 +80,17 @@ public interface ITurtleUpgrade extends UpgradeBase {
*/
default void update(ITurtleAccess turtle, TurtleSide side) {
}
/**
* Get upgrade data that should be persisted when the turtle was broken.
* <p>
* This method should be overridden when you don't need to store all upgrade data by default. For instance, if you
* store peripheral state in the upgrade data, which should be lost when the turtle is broken.
*
* @param upgradeData Data that currently stored for this upgrade
* @return Filtered version of this data.
*/
default CompoundTag getPersistedData(CompoundTag upgradeData) {
return upgradeData;
}
}

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.turtle;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack;
/**
* Indicates if an equipped turtle item will consume durability.
*
* @see TurtleUpgradeDataProvider.ToolBuilder#consumeDurability(TurtleToolDurability)
*/
public enum TurtleToolDurability implements StringRepresentable {
/**
* The equipped tool always consumes durability when using.
*/
ALWAYS("always"),
/**
* The equipped tool consumes durability if it is {@linkplain ItemStack#isEnchanted() enchanted} or has
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
*/
WHEN_ENCHANTED("when_enchanted"),
/**
* The equipped tool never consumes durability. Tools which have been damaged cannot be used as upgrades.
*/
NEVER("never");
private final String serialisedName;
/**
* The codec which may be used for serialising/deserialising {@link TurtleToolDurability}s.
*/
@SuppressWarnings("deprecation")
public static final StringRepresentable.EnumCodec<TurtleToolDurability> CODEC = StringRepresentable.fromEnum(TurtleToolDurability::values);
TurtleToolDurability(String serialisedName) {
this.serialisedName = serialisedName;
}
@Override
public String getSerializedName() {
return serialisedName;
}
}

View File

@@ -13,8 +13,10 @@ import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import javax.annotation.Nullable;
@@ -33,7 +35,7 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
private static final ResourceLocation TOOL_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "tool");
public TurtleUpgradeDataProvider(PackOutput output) {
super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.REGISTRY_ID);
super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.registryId());
}
/**
@@ -61,6 +63,8 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
private @Nullable Item craftingItem;
private @Nullable Float damageMultiplier = null;
private @Nullable TagKey<Block> breakable;
private boolean allowEnchantments = false;
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
this.id = id;
@@ -104,6 +108,28 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
return this;
}
/**
* Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
*
* @return The tool builder, for further use.
*/
public ToolBuilder allowEnchantments() {
allowEnchantments = true;
return this;
}
/**
* Set when the tool will consume durability.
*
* @param durability The durability predicate.
* @return The tool builder, for further use.
*/
public ToolBuilder consumeDurability(TurtleToolDurability durability) {
consumeDurability = durability;
return this;
}
/**
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
@@ -132,6 +158,10 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
}
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
if (breakable != null) s.addProperty("breakable", breakable.location().toString());
if (allowEnchantments) s.addProperty("allowEnchantments", true);
if (consumeDurability != TurtleToolDurability.NEVER) {
s.addProperty("consumeDurability", consumeDurability.getSerializedName());
}
}));
}
}

View File

@@ -7,6 +7,7 @@ package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
@@ -66,9 +67,21 @@ import java.util.function.Function;
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
*
* @deprecated Use {@link #registryId()} instead.
*/
@Deprecated(forRemoval = true)
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
/**
* The ID for the associated registry.
*
* @return The registry key.
*/
static ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> registryId() {
return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
}
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.

View File

@@ -4,10 +4,14 @@
package dan200.computercraft.api.upgrades;
import dan200.computercraft.api.pocket.IPocketAccess;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
@@ -50,6 +54,42 @@ public interface UpgradeBase {
*/
ItemStack getCraftingItem();
/**
* Returns the item stack representing a currently equipped turtle upgrade.
* <p>
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} and
* {@link IPocketAccess#getUpgradeNBTData()}}, by default this data is discarded when an upgrade is unequipped,
* and the original item stack is returned.
* <p>
* By overriding this method, you can create a new {@link ItemStack} which contains enough data to
* {@linkplain #getUpgradeData(ItemStack) re-create the upgrade data} if the item is re-equipped.
* <p>
* When overriding this, you should override {@link #getUpgradeData(ItemStack)} and {@link #isItemSuitable(ItemStack)}
* at the same time,
*
* @param upgradeData The current upgrade data. This should <strong>NOT</strong> be mutated.
* @return The item stack returned when unequipping.
*/
default ItemStack getUpgradeItem(CompoundTag upgradeData) {
return getCraftingItem();
}
/**
* Extract upgrade data from an {@link ItemStack}.
* <p>
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
* {@link IPocketAccess#getUpgradeNBTData()}.
* <p>
* This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
*
* @param stack The stack that was equipped by the turtle or pocket computer. This will have the same item as
* {@link #getCraftingItem()}.
* @return The upgrade data that should be set on the turtle or pocket computer.
*/
default CompoundTag getUpgradeData(ItemStack stack) {
return new CompoundTag();
}
/**
* Determine if an item is suitable for being used for this upgrade.
* <p>

View File

@@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.upgrades;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nullable;
/**
* An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.
* <p>
* <strong>IMPORTANT:</strong> The {@link #data()} in an upgrade data is often a reference to the original upgrade data.
* Be careful to take a {@linkplain #copy() defensive copy} if you plan to use the data in this upgrade.
*
* @param upgrade The current upgrade.
* @param data The upgrade's data.
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
*/
public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
/**
* A utility method to construct a new {@link UpgradeData} instance.
*
* @param upgrade An upgrade.
* @param data The upgrade's data.
* @param <T> The type of upgrade.
* @return The new {@link UpgradeData} instance.
*/
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
return new UpgradeData<>(upgrade, data);
}
/**
* Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
*
* @param upgrade The upgrade instance.
* @param <T> The type of upgrade.
* @return The default upgrade data.
*/
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
}
/**
* Take a copy of a (possibly {@code null}) {@link UpgradeData} instance.
*
* @param upgrade The copied upgrade data.
* @param <T> The type of upgrade.
* @return The newly created upgrade data.
*/
@Contract("!null -> !null; null -> null")
public static <T extends UpgradeBase> @Nullable UpgradeData<T> copyOf(@Nullable UpgradeData<T> upgrade) {
return upgrade == null ? null : upgrade.copy();
}
/**
* Get the {@linkplain UpgradeBase#getUpgradeItem(CompoundTag) upgrade item} for this upgrade.
* <p>
* This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original
* {@linkplain UpgradeBase#getCraftingItem() upgrade stack}.
*
* @return This upgrade's item.
*/
public ItemStack getUpgradeItem() {
return upgrade.getUpgradeItem(data).copy();
}
/**
* Take a copy of this {@link UpgradeData}. This returns a new instance with the same upgrade and a fresh copy of
* the upgrade data.
*
* @return A copy of the current instance.
*/
public UpgradeData<T> copy() {
return new UpgradeData<>(upgrade(), data().copy());
}
}

View File

@@ -23,10 +23,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -104,7 +101,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
@Override
public final CompletableFuture<?> run(CachedOutput cache) {
public CompletableFuture<?> run(CachedOutput cache) {
var base = output.getOutputFolder().resolve("data");
Set<ResourceLocation> seen = new HashSet<>();
@@ -127,7 +124,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
}
});
this.upgrades = upgrades;
this.upgrades = Collections.unmodifiableList(upgrades);
return Util.sequenceFailFast(futures);
}
@@ -166,5 +163,21 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
public void add(Consumer<Upgrade<R>> add) {
add.accept(this);
}
/**
* Return a new {@link Upgrade} which requires the given mod to be present.
* <p>
* This uses mod-loader-specific hooks (Forge's crafting conditions and Fabric's resource conditions). If using
* this in a multi-loader setup, you must generate resources separately for the two loaders.
*
* @param modId The id of the mod.
* @return A new upgrade instance.
*/
public Upgrade<R> requireMod(String modId) {
return new Upgrade<>(id, serialiser, json -> {
PlatformHelper.get().addRequiredModCondition(json, modId);
serialise.accept(json);
});
}
}
}

View File

@@ -15,10 +15,14 @@ import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
@@ -63,6 +67,10 @@ public interface ComputerCraftAPIService {
void registerRefuelHandler(TurtleRefuelHandler handler);
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId();
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId();
DetailRegistry<ItemStack> getItemStackDetailRegistry();
DetailRegistry<BlockReference> getBlockInWorldDetailRegistry();

View File

@@ -4,6 +4,8 @@
package dan200.computercraft.impl;
import com.google.gson.JsonObject;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
@@ -63,6 +65,15 @@ public interface PlatformHelper {
return item.getTag();
}
/**
* Add a resource condition which requires a mod to be loaded. This should be used by data providers such as
* {@link UpgradeDataProvider}.
*
* @param object The JSON object we're generating.
* @param modId The mod ID that we require.
*/
void addRequiredModCondition(JsonObject object, String modId);
final class Instance {
static final @Nullable PlatformHelper INSTANCE;
static final @Nullable Throwable ERROR;

View File

@@ -7,6 +7,7 @@ import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
plugins {
id("cc-tweaked.publishing")
id("cc-tweaked.vanilla")
id("cc-tweaked.gametest")
}
@@ -25,6 +26,7 @@ dependencies {
clientImplementation(clientClasses(project(":common-api")))
compileOnly(libs.bundles.externalMods.common)
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
compileOnly(libs.mixin)
annotationProcessorEverywhere(libs.autoService)

View File

@@ -174,7 +174,7 @@ public final class ClientHooks {
* @param addText A callback which adds a single line of text.
*/
public static void addGameDebugInfo(Consumer<String> addText) {
if (MonitorBlockEntityRenderer.hasRenderedThisFrame()) {
if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().options.renderDebug) {
addText.accept("[CC:T] Monitor renderer: " + MonitorBlockEntityRenderer.currentRenderer());
}
}

View File

@@ -13,6 +13,7 @@ import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
import dan200.computercraft.client.turtle.TurtleModemModeller;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.IColouredItem;
@@ -107,24 +108,6 @@ public final class ClientRegistry {
}
private static final String[] EXTRA_MODELS = new String[]{
// Turtle upgrades
"block/turtle_modem_normal_off_left",
"block/turtle_modem_normal_on_left",
"block/turtle_modem_normal_off_right",
"block/turtle_modem_normal_on_right",
"block/turtle_modem_advanced_off_left",
"block/turtle_modem_advanced_on_left",
"block/turtle_modem_advanced_off_right",
"block/turtle_modem_advanced_on_right",
"block/turtle_crafting_table_left",
"block/turtle_crafting_table_right",
"block/turtle_speaker_left",
"block/turtle_speaker_right",
// Turtle block renderer
"block/turtle_colour",
"block/turtle_elf_overlay",
"block/turtle_rainbow_overlay",
@@ -133,6 +116,7 @@ public final class ClientRegistry {
public static void registerExtraModels(Consumer<ResourceLocation> register) {
for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
TurtleUpgradeModellers.getDependencies().forEach(register);
}
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {

View File

@@ -11,6 +11,7 @@ import net.minecraft.ChatFormatting;
import net.minecraft.client.GuiMessageTag;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.components.ChatComponent;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import org.apache.commons.lang3.StringUtils;
@@ -18,6 +19,12 @@ import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nullable;
import java.util.Objects;
/**
* A {@link TableFormatter} subclass which writes directly to {@linkplain ChatComponent the chat GUI}.
* <p>
* Each message written gets a special {@link GuiMessageTag}, so we can remove the previous table of the same
* {@linkplain TableBuilder#getId() id}.
*/
public class ClientTableFormatter implements TableFormatter {
public static final ClientTableFormatter INSTANCE = new ClientTableFormatter();

View File

@@ -4,6 +4,11 @@
package dan200.computercraft.client;
/**
* Tracks the current client-side tick and frame.
* <p>
* These are updated via {@link ClientHooks}.
*/
public final class FrameInfo {
private static int tick;
private static long renderFrame;

View File

@@ -39,6 +39,12 @@ import java.util.concurrent.TimeUnit;
import static dan200.computercraft.core.util.Nullability.assertNonNull;
/**
* The base class of all screens with a computer terminal (i.e. {@link ComputerScreen}). This works with
* {@link AbstractComputerMenu} to handle the common behaviour such as the terminal, input and file uploading.
*
* @param <T> The concrete type of the associated menu.
*/
public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> extends AbstractContainerScreen<T> {
private static final Logger LOG = LoggerFactory.getLogger(AbstractComputerScreen.class);
@@ -55,6 +61,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
protected final int sidebarYOffset;
private long uploadNagDeadline = Long.MAX_VALUE;
private final int uploadMaxSize;
private final ItemStack displayStack;
public AbstractComputerScreen(T container, Inventory player, Component title, int sidebarYOffset) {
@@ -62,6 +69,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
terminalData = container.getTerminal();
family = container.getFamily();
displayStack = container.getDisplayStack();
uploadMaxSize = container.getUploadMaxSize();
input = new ClientInputHandler(menu);
this.sidebarYOffset = sidebarYOffset;
}
@@ -161,7 +169,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
try (var sbc = Files.newByteChannel(file)) {
var fileSize = sbc.size();
if (fileSize > UploadFileMessage.MAX_SIZE || (size += fileSize) >= UploadFileMessage.MAX_SIZE) {
if (fileSize > uploadMaxSize || (size += fileSize) >= uploadMaxSize) {
alert(UploadResult.FAILED_TITLE, UploadResult.TOO_MUCH_MSG);
return;
}

View File

@@ -16,9 +16,9 @@ import net.minecraft.world.inventory.AbstractContainerMenu;
import javax.annotation.Nullable;
/**
* An {@link InputHandler} which for use on the client.
* An {@link InputHandler} for use on the client.
* <p>
* This queues events on the remote player's open {@link ComputerMenu}
* This queues events on the remote player's open {@link ComputerMenu}.
*/
public final class ClientInputHandler implements InputHandler {
private final AbstractContainerMenu menu;

View File

@@ -15,6 +15,13 @@ import net.minecraft.world.entity.player.Inventory;
import static dan200.computercraft.client.render.ComputerBorderRenderer.BORDER;
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
/**
* A GUI for computers which renders the terminal (and border), but with no UI elements.
* <p>
* This is used by computers and pocket computers.
*
* @param <T> The concrete type of the associated menu.
*/
public final class ComputerScreen<T extends AbstractComputerMenu> extends AbstractComputerScreen<T> {
public ComputerScreen(T container, Inventory player, Component title) {
super(container, player, title, BORDER);

View File

@@ -12,7 +12,9 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;
/**
* The GUI for disk drives.
*/
public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/disk_drive.png");

View File

@@ -19,6 +19,11 @@ import javax.annotation.Nullable;
import static dan200.computercraft.core.util.Nullability.assertNonNull;
/**
* The GUI for off-hand computers. This accepts keyboard input, but does not render a terminal.
*
* @param <T> The concrete type of the associated menu.
*/
public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen implements MenuAccess<T> {
private final T menu;
private final Terminal terminalData;

View File

@@ -19,6 +19,11 @@ import java.util.List;
import static dan200.computercraft.core.util.Nullability.assertNonNull;
/**
* A screen which displays a series of buttons (such as a yes/no prompt).
* <p>
* When closed, it returns to the previous screen.
*/
public final class OptionScreen extends Screen {
private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/blank_screen.png");

View File

@@ -12,7 +12,9 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;
/**
* The GUI for printers.
*/
public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/printer.png");

View File

@@ -19,6 +19,11 @@ import org.lwjgl.glfw.GLFW;
import static dan200.computercraft.client.render.PrintoutRenderer.*;
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
/**
* The GUI for printed pages and books.
*
* @see dan200.computercraft.client.render.PrintoutRenderer
*/
public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
private final boolean book;
private final int pages;
@@ -46,8 +51,6 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
@Override
public boolean keyPressed(int key, int scancode, int modifiers) {
if (super.keyPressed(key, scancode, modifiers)) return true;
if (key == GLFW.GLFW_KEY_RIGHT) {
if (page < pages - 1) page++;
return true;
@@ -58,7 +61,7 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
return true;
}
return false;
return super.keyPressed(key, scancode, modifiers);
}
@Override

View File

@@ -19,13 +19,18 @@ import net.minecraft.world.entity.player.Inventory;
import static dan200.computercraft.shared.turtle.inventory.TurtleMenu.*;
/**
* The GUI for turtles.
*/
public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
private static final int TEX_WIDTH = 254;
private static final int TEX_WIDTH = 278;
private static final int TEX_HEIGHT = 217;
private static final int FULL_TEX_SIZE = 512;
public TurtleScreen(TurtleMenu container, Inventory player, Component title) {
super(container, player, title, BORDER);
@@ -42,16 +47,16 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) {
var advanced = family == ComputerFamily.ADVANCED;
RenderSystem.setShaderTexture(0, advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL);
blit(transform, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, TEX_WIDTH, TEX_HEIGHT);
blit(transform, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, FULL_TEX_SIZE, FULL_TEX_SIZE);
// Render selected slot
var slot = getMenu().getSelectedSlot();
if (slot >= 0) {
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
var slotX = slot % 4;
var slotY = slot / 4;
blit(transform,
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18,
0, 217, 24, 24
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0,
0, 217, 24, 24, FULL_TEX_SIZE, FULL_TEX_SIZE
);
}

View File

@@ -45,11 +45,11 @@ public final class ComputerSidebar {
x += CORNERS_BORDER + 1;
y += CORNERS_BORDER + ICON_MARGIN;
var turnOn = new HintedMessage(
var turnOn = new HintedMessage(Component.translatable("gui.computercraft.tooltip.turn_on"), (Component) null);
var turnOff = new HintedMessage(
Component.translatable("gui.computercraft.tooltip.turn_off"),
Component.translatable("gui.computercraft.tooltip.turn_off.key")
);
var turnOff = new HintedMessage(Component.translatable("gui.computercraft.tooltip.turn_on"), (Component) null);
add.accept(new DynamicImageButton(
x, y, ICON_WIDTH, ICON_HEIGHT, () -> isOn.getAsBoolean() ? 15 : 1, 1, ICON_TEX_Y_DIFF,
TEXTURE, TEX_SIZE, TEX_SIZE, b -> toggleComputer(isOn, input),

View File

@@ -68,14 +68,11 @@ public class DynamicImageButton extends Button {
RenderSystem.enableDepthTest();
}
@Override
public Component getMessage() {
return message.get().message;
}
@Override
public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
setTooltip(message.get().tooltip());
var message = this.message.get();
setMessage(message.message());
setTooltip(message.tooltip());
super.render(stack, mouseX, mouseY, partialTicks);
}

View File

@@ -15,6 +15,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.narration.NarratedElementType;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.network.chat.Component;
import org.lwjgl.glfw.GLFW;
@@ -25,6 +26,12 @@ import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH;
/**
* A widget which renders a computer terminal and handles input events (keyboard, mouse, clipboard) and computer
* shortcuts (terminate/shutdown/reboot).
*
* @see dan200.computercraft.client.gui.ClientInputHandler The input handler typically used with this class.
*/
public class TerminalWidget extends AbstractWidget {
private static final Component DESCRIPTION = Component.translatable("gui.computercraft.terminal");
@@ -75,6 +82,11 @@ public class TerminalWidget extends AbstractWidget {
@Override
public boolean keyPressed(int key, int scancode, int modifiers) {
if (key == GLFW.GLFW_KEY_ESCAPE) return false;
if (Screen.isPaste(key)) {
paste();
return true;
}
if ((modifiers & GLFW.GLFW_MOD_CONTROL) != 0) {
switch (key) {
case GLFW.GLFW_KEY_T -> {
@@ -86,32 +98,6 @@ public class TerminalWidget extends AbstractWidget {
case GLFW.GLFW_KEY_R -> {
if (rebootTimer < 0) rebootTimer = 0;
}
case GLFW.GLFW_KEY_V -> {
// Ctrl+V for paste
var clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
if (clipboard != null) {
// Clip to the first occurrence of \r or \n
var newLineIndex1 = clipboard.indexOf("\r");
var newLineIndex2 = clipboard.indexOf("\n");
if (newLineIndex1 >= 0 && newLineIndex2 >= 0) {
clipboard = clipboard.substring(0, Math.min(newLineIndex1, newLineIndex2));
} else if (newLineIndex1 >= 0) {
clipboard = clipboard.substring(0, newLineIndex1);
} else if (newLineIndex2 >= 0) {
clipboard = clipboard.substring(0, newLineIndex2);
}
// Filter the string
clipboard = SharedConstants.filterText(clipboard);
if (!clipboard.isEmpty()) {
// Clip to 512 characters and queue the event
if (clipboard.length() > 512) clipboard = clipboard.substring(0, 512);
computer.queueEvent("paste", new Object[]{ clipboard });
}
return true;
}
}
}
}
@@ -125,6 +111,29 @@ public class TerminalWidget extends AbstractWidget {
return true;
}
private void paste() {
var clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
// Clip to the first occurrence of \r or \n
var newLineIndex1 = clipboard.indexOf('\r');
var newLineIndex2 = clipboard.indexOf('\n');
if (newLineIndex1 >= 0 && newLineIndex2 >= 0) {
clipboard = clipboard.substring(0, Math.min(newLineIndex1, newLineIndex2));
} else if (newLineIndex1 >= 0) {
clipboard = clipboard.substring(0, newLineIndex1);
} else if (newLineIndex2 >= 0) {
clipboard = clipboard.substring(0, newLineIndex2);
}
// Filter the string
clipboard = SharedConstants.filterText(clipboard);
if (!clipboard.isEmpty()) {
// Clip to 512 characters and queue the event
if (clipboard.length() > 512) clipboard = clipboard.substring(0, 512);
computer.queueEvent("paste", new Object[]{ clipboard });
}
}
@Override
public boolean keyReleased(int key, int scancode, int modifiers) {
// Queue the "key_up" event and remove from the down set

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.integration.emi;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dev.emi.emi.api.EmiEntrypoint;
import dev.emi.emi.api.EmiPlugin;
import dev.emi.emi.api.EmiRegistry;
import dev.emi.emi.api.stack.Comparison;
import net.minecraft.world.item.ItemStack;
import java.util.function.BiPredicate;
@EmiEntrypoint
public class EMIComputerCraft implements EmiPlugin {
@Override
public void register(EmiRegistry registry) {
registry.setDefaultComparison(ModRegistry.Items.TURTLE_NORMAL.get(), turtleComparison);
registry.setDefaultComparison(ModRegistry.Items.TURTLE_ADVANCED.get(), turtleComparison);
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), pocketComparison);
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), pocketComparison);
}
private static final Comparison turtleComparison = compareStacks((left, right) ->
left.getItem() instanceof TurtleItem turtle
&& turtle.getUpgrade(left, TurtleSide.LEFT) == turtle.getUpgrade(right, TurtleSide.LEFT)
&& turtle.getUpgrade(left, TurtleSide.RIGHT) == turtle.getUpgrade(right, TurtleSide.RIGHT));
private static final Comparison pocketComparison = compareStacks((left, right) ->
left.getItem() instanceof PocketComputerItem && PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
private static Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) {
return Comparison.of((left, right) -> {
ItemStack leftStack = left.getItemStack(), rightStack = right.getItemStack();
return leftStack.getItem() == rightStack.getItem() && test.test(leftStack, rightStack);
});
}
}

View File

@@ -0,0 +1,103 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model.turtle;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.blaze3d.vertex.VertexFormatElement;
import com.mojang.math.Transformation;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.Direction;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* Applies a {@link Transformation} (or rather a {@link Matrix4f}) to a list of {@link BakedQuad}s.
* <p>
* This does a little bit of magic compared with other system (i.e. Forge's {@code QuadTransformers}), as it needs to
* handle flipping models upside down.
* <p>
* This is typically used with a {@link BakedModel} subclass - see the loader-specific projects.
*/
public class ModelTransformer {
private static final int[] INVERSE_ORDER = new int[]{ 3, 2, 1, 0 };
private static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
protected final Matrix4f transformation;
protected final boolean invert;
private @Nullable TransformedQuads cache;
public ModelTransformer(Transformation transformation) {
this.transformation = transformation.getMatrix();
invert = transformation.getMatrix().determinant() < 0;
}
public List<BakedQuad> transform(List<BakedQuad> quads) {
if (quads.isEmpty()) return List.of();
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
// so it's not worth being smarter here.
var cache = this.cache;
if (cache != null && quads.equals(cache.original())) return cache.transformed();
List<BakedQuad> transformed = new ArrayList<>(quads.size());
for (var quad : quads) transformed.add(transformQuad(quad));
this.cache = new TransformedQuads(quads, transformed);
return transformed;
}
private BakedQuad transformQuad(BakedQuad quad) {
var inputData = quad.getVertices();
var outputData = new int[inputData.length];
for (var i = 0; i < 4; i++) {
var inStart = STRIDE * i;
// Reverse the order of the quads if we're inverting
var outStart = getVertexOffset(i, invert);
System.arraycopy(inputData, inStart, outputData, outStart, STRIDE);
// Apply the matrix to our position
var inPosStart = inStart + POS_OFFSET;
var outPosStart = outStart + POS_OFFSET;
var x = Float.intBitsToFloat(inputData[inPosStart]);
var y = Float.intBitsToFloat(inputData[inPosStart + 1]);
var z = Float.intBitsToFloat(inputData[inPosStart + 2]);
// Transform the position
var pos = new Vector4f(x, y, z, 1);
transformation.transformProject(pos);
outputData[outPosStart] = Float.floatToRawIntBits(pos.x());
outputData[outPosStart + 1] = Float.floatToRawIntBits(pos.y());
outputData[outPosStart + 2] = Float.floatToRawIntBits(pos.z());
}
var direction = Direction.rotate(transformation, quad.getDirection());
return new BakedQuad(outputData, quad.getTintIndex(), direction, quad.getSprite(), quad.isShade());
}
public static int getVertexOffset(int vertex, boolean invert) {
return (invert ? ModelTransformer.INVERSE_ORDER[vertex] : vertex) * ModelTransformer.STRIDE;
}
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
}
private static int findOffset(VertexFormat format, VertexFormatElement element) {
var offset = 0;
for (var other : format.getElements()) {
if (other == element) return offset / Integer.BYTES;
offset += element.getByteSize();
}
throw new IllegalArgumentException("Cannot find " + element + " in " + format);
}
}

View File

@@ -4,11 +4,13 @@
package dan200.computercraft.client.model.turtle;
import com.google.common.cache.CacheBuilder;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Transformation;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
@@ -21,15 +23,17 @@ import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* Combines several individual models together to form a turtle.
*
* @param <T> The type of the resulting "baked model".
*/
public final class TurtleModelParts {
public final class TurtleModelParts<T> {
private static final Transformation identity, flip;
static {
@@ -42,33 +46,67 @@ public final class TurtleModelParts {
flip = new Transformation(stack.last().pose());
}
public record Combination(
private record Combination(
boolean colour,
@Nullable ITurtleUpgrade leftUpgrade,
@Nullable ITurtleUpgrade rightUpgrade,
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade,
@Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
@Nullable ResourceLocation overlay,
boolean christmas,
boolean flip
) {
Combination copy() {
if (leftUpgrade == null && rightUpgrade == null) return this;
return new Combination(
colour, UpgradeData.copyOf(leftUpgrade), UpgradeData.copyOf(rightUpgrade),
overlay, christmas, flip
);
}
}
private final BakedModel familyModel;
private final BakedModel colourModel;
private final Function<TransformedModel, BakedModel> transformer;
private final Function<Combination, T> buildModel;
/**
* A cache of {@link TransformedModel} to the transformed {@link BakedModel}. This helps us pool the transformed
* instances, reducing memory usage and hopefully ensuring their caches are hit more often!
*/
private final Map<TransformedModel, BakedModel> transformCache = new HashMap<>();
private final Map<TransformedModel, BakedModel> transformCache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.expireAfterAccess(30, TimeUnit.SECONDS)
.<TransformedModel, BakedModel>build()
.asMap();
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer) {
/**
* A cache of {@link Combination}s to the combined model.
*/
private final Map<Combination, T> modelCache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.expireAfterAccess(30, TimeUnit.SECONDS)
.<Combination, T>build()
.asMap();
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer, Function<List<BakedModel>, T> combineModel) {
this.familyModel = familyModel;
this.colourModel = colourModel;
this.transformer = x -> transformer.transform(x.getModel(), x.getMatrix());
buildModel = x -> combineModel.apply(buildModel(x));
}
public Combination getCombination(ItemStack stack) {
public T getModel(ItemStack stack) {
var combination = getCombination(stack);
var existing = modelCache.get(combination);
if (existing != null) return existing;
// Take a defensive copy of the upgrade data, and add it to the cache.
var newCombination = combination.copy();
var newModel = buildModel.apply(newCombination);
modelCache.put(newCombination, newModel);
return newModel;
}
private Combination getCombination(ItemStack stack) {
var christmas = Holiday.getCurrent() == Holiday.CHRISTMAS;
if (!(stack.getItem() instanceof TurtleItem turtle)) {
@@ -76,8 +114,8 @@ public final class TurtleModelParts {
}
var colour = turtle.getColour(stack);
var leftUpgrade = turtle.getUpgrade(stack, TurtleSide.LEFT);
var rightUpgrade = turtle.getUpgrade(stack, TurtleSide.RIGHT);
var leftUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.LEFT);
var rightUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.RIGHT);
var overlay = turtle.getOverlay(stack);
var label = turtle.getLabel(stack);
var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
@@ -85,7 +123,7 @@ public final class TurtleModelParts {
return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip);
}
public List<BakedModel> buildModel(Combination combo) {
private List<BakedModel> buildModel(Combination combo) {
var mc = Minecraft.getInstance();
var modelManager = mc.getItemRenderer().getItemModelShaper().getModelManager();
@@ -97,19 +135,20 @@ public final class TurtleModelParts {
if (overlayModelLocation != null) {
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
}
if (combo.leftUpgrade() != null) {
var model = TurtleUpgradeModellers.getModel(combo.leftUpgrade(), null, TurtleSide.LEFT);
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
}
if (combo.rightUpgrade() != null) {
var model = TurtleUpgradeModellers.getModel(combo.rightUpgrade(), null, TurtleSide.RIGHT);
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
}
addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade());
addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade());
return parts;
}
public BakedModel transform(BakedModel model, Transformation transformation) {
private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
if (upgrade == null) return;
var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
}
private BakedModel transform(BakedModel model, Transformation transformation) {
if (transformation.equals(Transformation.identity())) return model;
return transformCache.computeIfAbsent(new TransformedModel(model, transformation), transformer);
}

View File

@@ -4,8 +4,13 @@
package dan200.computercraft.client.platform;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.resources.model.BakedModel;
import javax.annotation.Nullable;
public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper {
static ClientPlatformHelper get() {
@@ -18,4 +23,16 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
* @param message The message to send.
*/
void sendToServer(NetworkMessage<ServerNetworkContext> message);
/**
* Render a {@link BakedModel}, using any loader-specific hooks.
*
* @param transform The current matrix transformation to apply.
* @param buffers The current pool of render buffers.
* @param model The model to draw.
* @param lightmapCoord The current packed lightmap coordinate.
* @param overlayLight The current overlay light.
* @param tints Block colour tints to apply to the model.
*/
void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints);
}

View File

@@ -13,6 +13,10 @@ import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import org.joml.Matrix4f;
/**
* Renders the borders of computers, either for a GUI ({@link dan200.computercraft.client.gui.ComputerScreen}) or
* {@linkplain PocketItemRenderer in-hand pocket computers}.
*/
public class ComputerBorderRenderer {
public static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_normal.png");
public static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_advanced.png");

View File

@@ -9,14 +9,19 @@ import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ItemInHandRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
/**
* A base class for items which have map-like rendering when held in the hand.
*
* @see dan200.computercraft.client.ClientHooks#onRenderHeldItem(PoseStack, MultiBufferSource, int, InteractionHand, float, float, float, ItemStack)
*/
public abstract class ItemMapLikeRenderer {
/**
* The main rendering method for the item.
@@ -25,7 +30,7 @@ public abstract class ItemMapLikeRenderer {
* @param render The buffer to render to
* @param stack The stack to render
* @param light The packed lightmap coordinates.
* @see ItemInHandRenderer#renderItem(LivingEntity, ItemStack, ItemTransforms.TransformType, boolean, PoseStack, MultiBufferSource, int)
* @see ItemInHandRenderer#renderItem(LivingEntity, ItemStack, ItemDisplayContext, boolean, PoseStack, MultiBufferSource, int)
*/
protected abstract void renderItem(PoseStack transform, MultiBufferSource render, ItemStack stack, int light);

View File

@@ -0,0 +1,103 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.item.ItemStack;
import org.joml.Vector4f;
import javax.annotation.Nullable;
import java.util.List;
/**
* Utilities for rendering {@link BakedModel}s and {@link BakedQuad}s.
*/
public final class ModelRenderer {
private ModelRenderer() {
}
/**
* Render a list of {@linkplain BakedQuad quads} to a buffer.
* <p>
* This is not intended to be used directly, but instead by {@link ClientPlatformHelper#renderBakedModel(PoseStack, MultiBufferSource, BakedModel, int, int, int[])}. The
* implementation here is pretty similar to {@link ItemRenderer#renderQuadList(PoseStack, VertexConsumer, List, ItemStack, int, int)},
* but supports inverted quads (i.e. those with a negative scale).
*
* @param transform The current matrix transformation to apply.
* @param buffer The buffer to draw to.
* @param quads The quads to draw.
* @param lightmapCoord The current packed lightmap coordinate.
* @param overlayLight The current overlay light.
* @param tints Block colour tints to apply to the model.
*/
public static void renderQuads(PoseStack transform, VertexConsumer buffer, List<BakedQuad> quads, int lightmapCoord, int overlayLight, @Nullable int[] tints) {
var matrix = transform.last();
var inverted = matrix.pose().determinant() < 0;
for (var bakedquad : quads) {
var tint = -1;
if (tints != null && bakedquad.isTinted()) {
var idx = bakedquad.getTintIndex();
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
}
var r = (float) (tint >> 16 & 255) / 255.0F;
var g = (float) (tint >> 8 & 255) / 255.0F;
var b = (float) (tint & 255) / 255.0F;
putBulkQuad(buffer, matrix, bakedquad, r, g, b, lightmapCoord, overlayLight, inverted);
}
}
/**
* A version of {@link VertexConsumer#putBulkData(PoseStack.Pose, BakedQuad, float, float, float, int, int)} which
* will reverse vertex order when the matrix is inverted.
*
* @param buffer The buffer to draw to.
* @param pose The current matrix stack.
* @param quad The quad to draw.
* @param red The red tint of this quad.
* @param green The green tint of this quad.
* @param blue The blue tint of this quad.
* @param lightmapCoord The lightmap coordinate
* @param overlayLight The overlay light.
* @param invert Whether to reverse the order of this quad.
*/
private static void putBulkQuad(VertexConsumer buffer, PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int lightmapCoord, int overlayLight, boolean invert) {
var matrix = pose.pose();
// It's a little dubious to transform using this matrix rather than the normal matrix. This mirrors the logic in
// Direction.rotate (so not out of nowhere!), but is a little suspicious.
var dirNormal = quad.getDirection().getNormal();
var vector = new Vector4f();
matrix.transform(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f, vector).normalize();
float normalX = vector.x(), normalY = vector.y(), normalZ = vector.z();
var vertices = quad.getVertices();
for (var vertex = 0; vertex < 4; vertex++) {
var i = ModelTransformer.getVertexOffset(vertex, invert);
var x = Float.intBitsToFloat(vertices[i]);
var y = Float.intBitsToFloat(vertices[i + 1]);
var z = Float.intBitsToFloat(vertices[i + 2]);
matrix.transform(x, y, z, 1, vector);
var u = Float.intBitsToFloat(vertices[i + 4]);
var v = Float.intBitsToFloat(vertices[i + 5]);
buffer.vertex(
vector.x(), vector.y(), vector.z(),
red, green, blue, 1.0F, u, v, overlayLight, lightmapCoord,
normalX, normalY, normalZ
);
}
}
}

View File

@@ -15,6 +15,10 @@ import org.joml.Matrix4f;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.shared.media.items.PrintoutItem.LINES_PER_PAGE;
/**
* Renders printed pages or books, either for a GUI ({@link dan200.computercraft.client.gui.PrintoutScreen}) or
* {@linkplain PrintoutItemRenderer in-hand/item frame printouts}.
*/
public final class PrintoutRenderer {
private static final float BG_SIZE = 256.0f;

View File

@@ -22,6 +22,9 @@ import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* Shared {@link RenderType}s used throughout the mod.
*/
public class RenderTypes {
public static final int FULL_BRIGHT_LIGHTMAP = (0xF << 4) | (0xF << 20);

View File

@@ -5,7 +5,6 @@
package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import com.mojang.math.Transformation;
import dan200.computercraft.api.ComputerCraftAPI;
@@ -14,25 +13,20 @@ import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import javax.annotation.Nullable;
import java.util.List;
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
@@ -40,8 +34,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
private final RandomSource random = RandomSource.create(0);
private final BlockEntityRenderDispatcher renderer;
private final Font font;
@@ -107,23 +99,22 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
var family = turtle.getFamily();
var overlay = turtle.getOverlay();
var buffer = buffers.getBuffer(Sheets.translucentCullBlockSheet());
renderModel(transform, buffer, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
// Render the overlay
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
if (overlayModel != null) {
renderModel(transform, buffer, lightmapCoord, overlayLight, overlayModel, null);
renderModel(transform, buffers, lightmapCoord, overlayLight, overlayModel, null);
}
// Render the upgrades
renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
transform.popPose();
}
private void renderUpgrade(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
private void renderUpgrade(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
var upgrade = turtle.getUpgrade(side);
if (upgrade == null) return;
transform.pushPose();
@@ -134,46 +125,33 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
transform.translate(0.0f, -0.5f, -0.5f);
var model = TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side);
pushPoseFromTransformation(transform, model.getMatrix());
renderModel(transform, renderer, lightmapCoord, overlayLight, model.getModel(), null);
transform.popPose();
applyTransformation(transform, model.getMatrix());
renderModel(transform, buffers, lightmapCoord, overlayLight, model.getModel(), null);
transform.popPose();
}
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getModelManager();
renderModel(transform, renderer, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
renderModel(transform, buffers, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
}
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
random.setSeed(0);
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
for (var facing : DirectionUtil.FACINGS) {
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random), tints);
}
/**
* Render a block model.
*
* @param transform The current matrix stack.
* @param renderer The buffer to write to.
* @param lightmapCoord The current lightmap coordinate.
* @param overlayLight The overlay light.
* @param model The model to render.
* @param tints Tints for the quads, as an array of RGB values.
* @see net.minecraft.client.renderer.block.ModelBlockRenderer#renderModel
*/
private void renderModel(PoseStack transform, MultiBufferSource renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
ClientPlatformHelper.get().renderBakedModel(transform, renderer, model, lightmapCoord, overlayLight, tints);
}
private static void renderQuads(PoseStack transform, VertexConsumer buffer, int lightmapCoord, int overlayLight, List<BakedQuad> quads, @Nullable int[] tints) {
var matrix = transform.last();
for (var bakedquad : quads) {
var tint = -1;
if (tints != null && bakedquad.isTinted()) {
var idx = bakedquad.getTintIndex();
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
}
var r = (float) (tint >> 16 & 255) / 255.0F;
var g = (float) (tint >> 8 & 255) / 255.0F;
var b = (float) (tint & 255) / 255.0F;
buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
}
}
private static void pushPoseFromTransformation(PoseStack stack, Transformation transformation) {
stack.pushPose();
private static void applyTransformation(PoseStack stack, Transformation transformation) {
var trans = transformation.getTranslation();
stack.translate(trans.x(), trans.y(), trans.z());

View File

@@ -20,6 +20,13 @@ import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Set;
/**
* Holds the client-side state of a monitor. This both tracks the last place a monitor was rendered at (see the comments
* in {@link MonitorBlockEntityRenderer}) and the current OpenGL buffers allocated for this object.
* <p>
* This is automatically cleared by {@link dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity} when the
* entity is unloaded on the client side (see {@link MonitorRenderState#close()}).
*/
public class MonitorRenderState implements ClientMonitor.RenderState {
@GuardedBy("allMonitors")
private static final Set<MonitorRenderState> allMonitors = new HashSet<>();

View File

@@ -7,6 +7,7 @@ package dan200.computercraft.client.render.monitor;
import com.mojang.blaze3d.shaders.Uniform;
import com.mojang.blaze3d.vertex.VertexFormat;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
@@ -24,6 +25,16 @@ import java.nio.ByteBuffer;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.getColour;
/**
* The shader used for the monitor TBO renderer.
* <p>
* This extends Minecraft's default shader loading code to extract out the TBO buffer and handle our custom uniforms
* ({@code MonitorData}, {@code CursorBlink}).
* <p>
* See also {@code monitor_tbo.fsh} and {@code monitor_tbo.vsh} in the mod's resources.
*
* @see RenderTypes#getMonitorTextureBufferShader()
*/
public class MonitorTextureBufferShader extends ShaderInstance {
public static final int UNIFORM_SIZE = 4 * 4 * 16 + 4 + 4 + 2 * 4 + 4;

View File

@@ -13,13 +13,18 @@ import org.lwjgl.BufferUtils;
import javax.annotation.Nullable;
import javax.sound.sampled.AudioFormat;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.Executor;
/**
* An {@link AudioStream} which decodes DFPWM streams, converting them to PCM.
*
* @see SpeakerPeripheral Server-side encoding of the audio.
* @see SpeakerInstance
*/
class DfpwmStream implements AudioStream {
private static final int PREC = 10;
private static final int LPF_STRENGTH = 140;
@@ -128,7 +133,7 @@ class DfpwmStream implements AudioStream {
}
@Override
public void close() throws IOException {
public void close() {
buffers.clear();
}

View File

@@ -20,6 +20,14 @@ import net.minecraft.world.entity.Entity;
import javax.annotation.Nullable;
import java.util.concurrent.CompletableFuture;
/**
* A sound played by a speaker. This has two purposes:
*
* <ul>
* <li>Tracks a {@link SpeakerPosition}, ensuring the sound moves around with the speaker's owner.</li>
* <li>Provides a {@link DfpwmStream} when playing custom audio.</li>
* </ul>
*/
public class SpeakerSound extends AbstractSoundInstance implements TickableSoundInstance {
@Nullable
DfpwmStream stream;

View File

@@ -13,6 +13,12 @@ import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
/**
* A {@link TurtleUpgradeModeller} for modems, providing different models depending on if the modem is on/off.
*/
public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
private final ResourceLocation leftOffModel;
private final ResourceLocation rightOffModel;
@@ -45,4 +51,9 @@ public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
? TransformedModel.of(active ? leftOnModel : leftOffModel)
: TransformedModel.of(active ? rightOnModel : rightOffModel);
}
@Override
public Collection<ResourceLocation> getDependencies() {
return List.of(leftOffModel, rightOffModel, leftOnModel, rightOnModel);
}
}

View File

@@ -14,12 +14,19 @@ import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.impl.UpgradeManager;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
/**
* A registry of {@link TurtleUpgradeModeller}s.
*
* @see dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller)
*/
public final class TurtleUpgradeModellers {
private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side) ->
new TransformedModel(Minecraft.getInstance().getModelManager().getMissingModel(), Transformation.identity());
@@ -47,12 +54,18 @@ public final class TurtleUpgradeModellers {
}
}
public static TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess access, TurtleSide side) {
public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) {
@SuppressWarnings("unchecked")
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, access, side);
}
public static TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
@SuppressWarnings("unchecked")
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, data, side);
}
private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {
var wrapper = TurtleUpgrades.instance().getWrapper(upgradeA);
if (wrapper == null) return NULL_TURTLE_MODELLER;
@@ -60,4 +73,8 @@ public final class TurtleUpgradeModellers {
var modeller = turtleModels.get(wrapper.serialiser());
return modeller == null ? NULL_TURTLE_MODELLER : modeller;
}
public static Stream<ResourceLocation> getDependencies() {
return turtleModels.values().stream().flatMap(x -> x.getDependencies().stream());
}
}

View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.client;
import dan200.computercraft.data.DataProviders;
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
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;
/**
* 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())
));
});
}
}

View File

@@ -37,6 +37,14 @@ import static net.minecraft.data.models.model.ModelLocationUtils.getModelLocatio
import static net.minecraft.data.models.model.TextureMapping.getBlockTexture;
class BlockModelProvider {
private static final TextureSlot CURSOR = TextureSlot.create("cursor");
private static final ModelTemplate COMPUTER_ON = new ModelTemplate(
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/computer_on")),
Optional.empty(),
TextureSlot.FRONT, TextureSlot.SIDE, TextureSlot.TOP, CURSOR
);
private static final ModelTemplate MONITOR_BASE = new ModelTemplate(
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/monitor_base")),
Optional.empty(),
@@ -142,11 +150,18 @@ class BlockModelProvider {
private static void registerComputer(BlockModelGenerators generators, ComputerBlock<?> block) {
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(block)
.with(createHorizontalFacingDispatch())
.with(createModelDispatch(ComputerBlock.STATE, state -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix(
block, "_" + state.getSerializedName(),
TextureMapping.orientableCube(block).put(TextureSlot.FRONT, getBlockTexture(block, "_front" + state.getTexture())),
generators.modelOutput
)))
.with(createModelDispatch(ComputerBlock.STATE, state -> switch (state) {
case OFF -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix(
block, "_" + state.getSerializedName(),
TextureMapping.orientableCube(block),
generators.modelOutput
);
case ON, BLINKING -> COMPUTER_ON.createWithSuffix(
block, "_" + state.getSerializedName(),
TextureMapping.orientableCube(block).put(CURSOR, new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/computer" + state.getTexture())),
generators.modelOutput
);
}))
);
generators.delegateItemModel(block, getModelLocation(block, "_blinking"));
}

View File

@@ -4,16 +4,20 @@
package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
import net.minecraft.data.models.BlockModelGenerators;
import net.minecraft.data.models.ItemModelGenerators;
import net.minecraft.data.tags.TagsProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -37,11 +41,22 @@ public final class DataProviders {
generator.models(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
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);
}
}
interface GeneratorSink {
public interface GeneratorSink {
<T extends DataProvider> T add(DataProvider.Factory<T> factory);
<T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output);
void lootTable(List<SubProviderEntry> tables);
TagsProvider<Block> blockTags(Consumer<TagProvider.TagConsumer<Block>> tags);

View File

@@ -6,6 +6,7 @@ package dan200.computercraft.data;
import com.google.gson.JsonObject;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
import dan200.computercraft.api.upgrades.UpgradeBase;
@@ -20,6 +21,7 @@ import dan200.computercraft.shared.platform.RegistryWrappers;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
@@ -97,6 +99,12 @@ public final class LanguageProvider implements DataProvider {
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), "Advanced Pocket Computer");
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().getDescriptionId() + ".upgraded", "Advanced %s Pocket Computer");
// Tags (for EMI)
add(ComputerCraftTags.Items.COMPUTER, "Computers");
add(ComputerCraftTags.Items.TURTLE, "Turtles");
add(ComputerCraftTags.Items.WIRED_MODEM, "Wired modems");
add(ComputerCraftTags.Items.MONITOR, "Monitors");
// Turtle/pocket upgrades
add("upgrade.minecraft.diamond_sword.adjective", "Melee");
add("upgrade.minecraft.diamond_shovel.adjective", "Digging");
@@ -150,9 +158,6 @@ public final class LanguageProvider implements DataProvider {
add("commands.computercraft.track.dump.desc", "Dump the latest results of computer tracking.");
add("commands.computercraft.track.dump.no_timings", "No timings available");
add("commands.computercraft.track.dump.computer", "Computer");
add("commands.computercraft.reload.synopsis", "Reload the ComputerCraft config file");
add("commands.computercraft.reload.desc", "Reload the ComputerCraft config file");
add("commands.computercraft.reload.done", "Reloaded config");
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.");
@@ -208,11 +213,13 @@ public final class LanguageProvider implements DataProvider {
// Config options
addConfigEntry(ConfigSpec.computerSpaceLimit, "Computer space limit (bytes)");
addConfigEntry(ConfigSpec.floppySpaceLimit, "Floppy Disk space limit (bytes)");
addConfigEntry(ConfigSpec.uploadMaxSize, "File upload size limit (bytes)");
addConfigEntry(ConfigSpec.maximumFilesOpen, "Maximum files open per computer");
addConfigEntry(ConfigSpec.disableLua51Features, "Disable Lua 5.1 features");
addConfigEntry(ConfigSpec.defaultComputerSettings, "Default Computer settings");
addConfigEntry(ConfigSpec.logComputerErrors, "Log computer errors");
addConfigEntry(ConfigSpec.commandRequireCreative, "Command computers require creative");
addConfigEntry(ConfigSpec.disabledGenericMethods, "Disabled generic methods");
addConfigGroup(ConfigSpec.serverSpec, "execution", "Execution");
addConfigEntry(ConfigSpec.computerThreads, "Computer threads");
@@ -229,6 +236,11 @@ public final class LanguageProvider implements DataProvider {
addConfigEntry(ConfigSpec.httpDownloadBandwidth, "Global download limit");
addConfigEntry(ConfigSpec.httpUploadBandwidth, "Global upload limit");
addConfigGroup(ConfigSpec.serverSpec, "http.proxy", "Proxy");
addConfigEntry(ConfigSpec.httpProxyHost, "Host name");
addConfigEntry(ConfigSpec.httpProxyPort, "Port");
addConfigEntry(ConfigSpec.httpProxyType, "Proxy type");
addConfigGroup(ConfigSpec.serverSpec, "peripheral", "Peripherals");
addConfigEntry(ConfigSpec.commandBlockEnabled, "Enable command block peripheral");
addConfigEntry(ConfigSpec.modemRange, "Modem range (default)");
@@ -271,8 +283,8 @@ public final class LanguageProvider implements DataProvider {
turtleUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),
getConfigEntries(ConfigSpec.serverSpec).map(ConfigFile.Entry::translationKey),
getConfigEntries(ConfigSpec.clientSpec).map(ConfigFile.Entry::translationKey)
ConfigSpec.serverSpec.entries().map(ConfigFile.Entry::translationKey),
ConfigSpec.clientSpec.entries().map(ConfigFile.Entry::translationKey)
).flatMap(x -> x);
}
@@ -292,6 +304,10 @@ public final class LanguageProvider implements DataProvider {
add(AggregatedMetric.TRANSLATION_PREFIX + metric.name() + ".name", text);
}
private void add(TagKey<Item> tag, String text) {
add("tag.item." + tag.location().getNamespace() + "." + tag.location().getPath(), text);
}
private void addConfigGroup(ConfigFile spec, String path, String text) {
var entry = spec.getEntry(path);
if (!(entry instanceof ConfigFile.Group)) throw new IllegalArgumentException("Cannot find group " + path);
@@ -302,16 +318,4 @@ public final class LanguageProvider implements DataProvider {
add(value.translationKey(), text);
add(value.translationKey() + ".tooltip", value.comment());
}
private static Stream<ConfigFile.Entry> getConfigEntries(ConfigFile spec) {
return spec.entries().flatMap(LanguageProvider::getConfigEntries);
}
private static Stream<ConfigFile.Entry> getConfigEntries(ConfigFile.Entry entry) {
if (entry instanceof ConfigFile.Value<?>) return Stream.of(entry);
if (entry instanceof ConfigFile.Group group) {
return Stream.concat(Stream.of(entry), group.children().flatMap(LanguageProvider::getConfigEntries));
}
throw new IllegalStateException("Invalid config entry " + entry);
}
}

View File

@@ -8,6 +8,7 @@ import com.google.gson.JsonObject;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.IColouredItem;
@@ -15,8 +16,8 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RecipeIngredients;
import dan200.computercraft.shared.platform.RegistryWrappers;
import dan200.computercraft.shared.pocket.items.PocketComputerItemFactory;
import dan200.computercraft.shared.turtle.items.TurtleItemFactory;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.util.ColourUtils;
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
import net.minecraft.advancements.critereon.ItemPredicate;
@@ -38,6 +39,7 @@ import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.Blocks;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
@@ -93,20 +95,23 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
}
private static List<TurtleItem> turtleItems() {
return List.of(ModRegistry.Items.TURTLE_NORMAL.get(), ModRegistry.Items.TURTLE_ADVANCED.get());
}
/**
* Register a crafting recipe for each turtle upgrade.
*
* @param add The callback to add recipes.
*/
private void turtleUpgrades(Consumer<FinishedRecipe> add) {
for (var family : ComputerFamily.values()) {
var base = TurtleItemFactory.create(-1, null, -1, family, null, null, 0, null);
if (base.isEmpty()) continue;
for (var turtleItem : turtleItems()) {
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
var nameId = family.name().toLowerCase(Locale.ROOT);
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
var result = TurtleItemFactory.create(-1, null, -1, family, null, upgrade, -1, null);
var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, result.getItem())
.group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
@@ -125,20 +130,24 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
}
private static List<PocketComputerItem> pocketComputerItems() {
return List.of(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get());
}
/**
* Register a crafting recipe for each pocket upgrade.
*
* @param add The callback to add recipes.
*/
private void pocketUpgrades(Consumer<FinishedRecipe> add) {
for (var family : ComputerFamily.values()) {
var base = PocketComputerItemFactory.create(-1, null, -1, family, null);
for (var pocket : pocketComputerItems()) {
var base = pocket.create(-1, null, -1, null);
if (base.isEmpty()) continue;
var nameId = family.name().toLowerCase(Locale.ROOT);
var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
var result = PocketComputerItemFactory.create(-1, null, -1, family, upgrade);
var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, result.getItem())
.group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
@@ -180,11 +189,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
private void turtleOverlay(Consumer<FinishedRecipe> add, String overlay, Consumer<ShapelessRecipeBuilder> build) {
for (var family : ComputerFamily.values()) {
var base = TurtleItemFactory.create(-1, null, -1, family, null, null, 0, null);
if (base.isEmpty()) continue;
for (var turtleItem : turtleItems()) {
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
var nameId = family.name().toLowerCase(Locale.ROOT);
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
var group = "%s:turtle_%s_overlay".formatted(ComputerCraftAPI.MOD_ID, nameId);
var builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.REDSTONE, base.getItem())

View File

@@ -4,6 +4,7 @@
package dan200.computercraft.impl;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.api.detail.DetailRegistry;
import dan200.computercraft.api.filesystem.Mount;
@@ -14,10 +15,10 @@ import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import dan200.computercraft.core.apis.ApiFactories;
import dan200.computercraft.core.asm.GenericMethod;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.core.filesystem.WritableFileMount;
import dan200.computercraft.impl.detail.DetailRegistryImpl;
import dan200.computercraft.impl.network.wired.WiredNodeImpl;
@@ -27,6 +28,8 @@ import dan200.computercraft.shared.details.BlockDetails;
import dan200.computercraft.shared.details.ItemDetails;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.ItemStack;
@@ -41,6 +44,9 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
private final DetailRegistry<ItemStack> itemStackDetails = new DetailRegistryImpl<>(ItemDetails::fillBasic);
private final DetailRegistry<BlockReference> blockDetails = new DetailRegistryImpl<>(BlockDetails::fillBasic);
protected static final ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
protected static final ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
public static @Nullable InputStream getResourceFile(MinecraftServer server, String domain, String subPath) {
var manager = server.getResourceManager();
var resource = manager.getResource(new ResourceLocation(domain, subPath)).orElse(null);
@@ -71,7 +77,7 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
@Override
public final void registerGenericSource(GenericSource source) {
GenericMethod.register(source);
GenericSources.register(source);
}
@Override
@@ -105,10 +111,20 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
}
@Override
public void registerRefuelHandler(TurtleRefuelHandler handler) {
public final void registerRefuelHandler(TurtleRefuelHandler handler) {
TurtleRefuelHandlers.register(handler);
}
@Override
public final ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId() {
return turtleUpgradeRegistryId;
}
@Override
public final ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId() {
return pocketUpgradeRegistryId;
}
@Override
public final DetailRegistry<ItemStack> getItemStackDetailRegistry() {
return itemStackDetails;

View File

@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.core.apis;
package dan200.computercraft.impl;
import dan200.computercraft.api.lua.ILuaAPIFactory;
@@ -11,19 +11,24 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Objects;
/**
* The global factory for {@link ILuaAPIFactory}s.
*
* @see dan200.computercraft.core.ComputerContext.Builder#apiFactories(Collection)
* @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
*/
public final class ApiFactories {
private ApiFactories() {
}
private static final Collection<ILuaAPIFactory> factories = new LinkedHashSet<>();
private static final Collection<ILuaAPIFactory> factoriesView = Collections.unmodifiableCollection(factories);
public static synchronized void register(ILuaAPIFactory factory) {
static synchronized void register(ILuaAPIFactory factory) {
Objects.requireNonNull(factory, "provider cannot be null");
factories.add(factory);
}
public static Iterable<ILuaAPIFactory> getAll() {
return factoriesView;
public static Collection<ILuaAPIFactory> getAll() {
return Collections.unmodifiableCollection(factories);
}
}

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.impl;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.core.asm.GenericMethod;
import dan200.computercraft.shared.config.ConfigSpec;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
/**
* The global registry for {@link GenericSource}s.
*
* @see dan200.computercraft.core.ComputerContext.Builder#genericMethods(Collection)
* @see dan200.computercraft.api.ComputerCraftAPI#registerGenericSource(GenericSource)
*/
public final class GenericSources {
private GenericSources() {
}
private static final Collection<GenericSource> sources = new LinkedHashSet<>();
static synchronized void register(GenericSource source) {
Objects.requireNonNull(source, "provider cannot be null");
sources.add(source);
}
public static Collection<GenericMethod> getAllMethods() {
var disabledMethods = Set.copyOf(ConfigSpec.disabledGenericMethods.get());
return sources.stream()
.filter(x -> !disabledMethods.contains(x.id()))
.flatMap(GenericMethod::getMethods)
.filter(x -> !disabledMethods.contains(x.id()))
.toList();
}
}

View File

@@ -12,7 +12,7 @@ import java.util.stream.Stream;
public final class PocketUpgrades {
private static final UpgradeManager<PocketUpgradeSerialiser<?>, IPocketUpgrade> registry = new UpgradeManager<>(
"pocket computer upgrade", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.REGISTRY_ID
"pocket computer upgrade", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.registryId()
);
private PocketUpgrades() {

View File

@@ -12,7 +12,7 @@ import java.util.stream.Stream;
public final class TurtleUpgrades {
private static final UpgradeManager<TurtleUpgradeSerialiser<?>, ITurtleUpgrade> registry = new UpgradeManager<>(
"turtle upgrade", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.REGISTRY_ID
"turtle upgrade", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.registryId()
);
private TurtleUpgrades() {

View File

@@ -7,6 +7,7 @@ package dan200.computercraft.impl;
import com.google.gson.*;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.Registry;
@@ -74,13 +75,13 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
}
@Nullable
public T get(ItemStack stack) {
public UpgradeData<T> get(ItemStack stack) {
if (stack.isEmpty()) return null;
for (var wrapper : current.values()) {
var craftingStack = wrapper.upgrade().getCraftingItem();
if (!craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade().isItemSuitable(stack)) {
return wrapper.upgrade();
return UpgradeData.of(wrapper.upgrade, wrapper.upgrade.getUpgradeData(stack));
}
}

View File

@@ -1,19 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Explosion;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import javax.annotation.Nullable;
@Mixin(Explosion.class)
public interface ExplosionAccessor {
@Nullable
@Accessor("source")
Entity computercraft$getExploder();
}

View File

@@ -11,6 +11,7 @@ import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.TurtleUpgrades;
@@ -255,7 +256,7 @@ public final class ModRegistry {
}
public static class TurtleSerialisers {
static final RegistrationHelper<TurtleUpgradeSerialiser<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(TurtleUpgradeSerialiser.REGISTRY_ID);
static final RegistrationHelper<TurtleUpgradeSerialiser<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(TurtleUpgradeSerialiser.registryId());
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> TurtleUpgradeSerialiser.simpleWithCustomItem(TurtleSpeaker::new));
@@ -270,7 +271,7 @@ public final class ModRegistry {
}
public static class PocketUpgradeSerialisers {
static final RegistrationHelper<PocketUpgradeSerialiser<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(PocketUpgradeSerialiser.REGISTRY_ID);
static final RegistrationHelper<PocketUpgradeSerialiser<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(PocketUpgradeSerialiser.registryId());
public static final RegistryEntry<PocketUpgradeSerialiser<PocketSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> PocketUpgradeSerialiser.simpleWithCustomItem(PocketSpeaker::new));
@@ -446,12 +447,12 @@ public final class ModRegistry {
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
out.accept(turtle.create(-1, null, -1, null, null, 0, null));
TurtleUpgrades.getVanillaUpgrades()
.map(x -> turtle.create(-1, null, -1, null, x, 0, null))
.map(x -> turtle.create(-1, null, -1, null, UpgradeData.ofDefault(x), 0, null))
.forEach(out::accept);
}
private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket) {
out.accept(pocket.create(-1, null, -1, null));
PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, x)).forEach(out::accept);
PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, UpgradeData.ofDefault(x))).forEach(out::accept);
}
}

View File

@@ -6,9 +6,12 @@ package dan200.computercraft.shared.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
@@ -64,7 +67,6 @@ public final class CommandComputerCraft {
var source = context.getSource();
List<ServerComputer> computers = new ArrayList<>(ServerContext.get(source.getServer()).registry().getComputers());
// Unless we're on a server, limit the number of rows we can send.
Level world = source.getLevel();
var pos = BlockPos.containing(source.getPosition());
@@ -125,7 +127,7 @@ public final class CommandComputerCraft {
if (computer.isOn()) shutdown++;
computer.shutdown();
}
context.getSource().sendSuccess(translate("commands.computercraft.shutdown.done", shutdown, computers.size()), false);
context.getSource().sendSuccess(Component.translatable("commands.computercraft.shutdown.done", shutdown, computers.size()), false);
return shutdown;
}))
@@ -139,7 +141,7 @@ public final class CommandComputerCraft {
if (!computer.isOn()) on++;
computer.turnOn();
}
context.getSource().sendSuccess(translate("commands.computercraft.turn_on.done", on, computers.size()), false);
context.getSource().sendSuccess(Component.translatable("commands.computercraft.turn_on.done", on, computers.size()), false);
return on;
}))
@@ -170,7 +172,10 @@ public final class CommandComputerCraft {
.then(command("queue")
.requires(UserLevel.ANYONE)
.arg("computer", manyComputers())
.arg(
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument("computer", manyComputers())
.suggests((context, builder) -> Suggestions.empty())
)
.argManyValue("args", StringArgumentType.string(), Collections.emptyList())
.executes((ctx, args) -> {
var computers = getComputersArgument(ctx, "computer");
@@ -214,8 +219,10 @@ public final class CommandComputerCraft {
getMetricsInstance(context.getSource()).start();
var stopCommand = "/computercraft track stop";
context.getSource().sendSuccess(translate("commands.computercraft.track.start.stop",
link(text(stopCommand), stopCommand, translate("commands.computercraft.track.stop.action"))), false);
context.getSource().sendSuccess(Component.translatable(
"commands.computercraft.track.start.stop",
link(text(stopCommand), stopCommand, Component.translatable("commands.computercraft.track.stop.action"))
), false);
return 1;
}))
@@ -255,7 +262,7 @@ public final class CommandComputerCraft {
out.append(link(
text(Integer.toString(serverComputer.getInstanceID())),
"/computercraft dump " + serverComputer.getInstanceID(),
translate("commands.computercraft.dump.action")
Component.translatable("commands.computercraft.dump.action")
));
}
@@ -269,13 +276,13 @@ public final class CommandComputerCraft {
.append(link(
text("\u261b"),
"/computercraft tp " + serverComputer.getInstanceID(),
translate("commands.computercraft.tp.action")
Component.translatable("commands.computercraft.tp.action")
))
.append(" ")
.append(link(
text("\u20e2"),
"/computercraft view " + serverComputer.getInstanceID(),
translate("commands.computercraft.view.action")
Component.translatable("commands.computercraft.view.action")
));
}
@@ -292,7 +299,7 @@ public final class CommandComputerCraft {
return link(
position(computer.getPosition()),
"/computercraft tp " + computer.getInstanceID(),
translate("commands.computercraft.tp.action")
Component.translatable("commands.computercraft.tp.action")
);
} else {
return position(computer.getPosition());
@@ -306,7 +313,7 @@ public final class CommandComputerCraft {
return link(
text("\u270E"),
"/" + OPEN_COMPUTER + id,
translate("commands.computercraft.dump.open_path")
Component.translatable("commands.computercraft.dump.open_path")
);
}
@@ -331,7 +338,7 @@ public final class CommandComputerCraft {
timings.sort(Comparator.<ComputerMetrics, Long>comparing(x -> x.get(sortField.metric(), sortField.aggregate())).reversed());
var headers = new Component[1 + fields.size()];
headers[0] = translate("commands.computercraft.track.dump.computer");
headers[0] = Component.translatable("commands.computercraft.track.dump.computer");
for (var i = 0; i < fields.size(); i++) headers[i + 1] = fields.get(i).displayName();
var table = new TableBuilder("Metrics", headers);

View File

@@ -49,6 +49,29 @@ public enum UserLevel implements Predicate<CommandSourceStack> {
return source.hasPermission(toLevel());
}
/**
* Take the union of two {@link UserLevel}s.
* <p>
* This satisfies the property that for all sources {@code s}, {@code a.test(s) || b.test(s) == (a b).test(s)}.
*
* @param left The first user level to take the union of.
* @param right The second user level to take the union of.
* @return The union of two levels.
*/
public static UserLevel union(UserLevel left, UserLevel right) {
if (left == right) return left;
// x ANYONE = ANYONE
if (left == ANYONE || right == ANYONE) return ANYONE;
// x OWNER = OWNER
if (left == OWNER) return right;
if (right == OWNER) return left;
// At this point, we have x != y and x, y { OP, OWNER_OP }.
return OWNER_OP;
}
private static boolean isOwner(CommandSourceStack source) {
var server = source.getServer();
var sender = source.getEntity();

View File

@@ -44,12 +44,12 @@ public class ArgumentUtils {
@SuppressWarnings("unchecked")
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void serializeToNetwork(FriendlyByteBuf buffer, ArgumentTypeInfo<A, T> type, ArgumentTypeInfo.Template<A> template) {
RegistryWrappers.writeId(buffer, RegistryWrappers.COMMAND_ARGUMENT_TYPES, type);
buffer.writeId(RegistryWrappers.COMMAND_ARGUMENT_TYPES, type);
type.serializeToNetwork((T) template, buffer);
}
public static ArgumentTypeInfo.Template<?> deserialize(FriendlyByteBuf buffer) {
var type = RegistryWrappers.readId(buffer, RegistryWrappers.COMMAND_ARGUMENT_TYPES);
var type = buffer.readById(RegistryWrappers.COMMAND_ARGUMENT_TYPES);
Objects.requireNonNull(type, "Unknown argument type");
return type.deserializeFromNetwork(buffer);
}

View File

@@ -18,7 +18,6 @@ import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.network.FriendlyByteBuf;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@@ -159,14 +158,14 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
}
@Override
public ComputersArgumentType.Template unpack(@NotNull ComputersArgumentType argumentType) {
public ComputersArgumentType.Template unpack(ComputersArgumentType argumentType) {
return new ComputersArgumentType.Template(this, argumentType.requireSome);
}
}
public record Template(Info info, boolean requireSome) implements ArgumentTypeInfo.Template<ComputersArgumentType> {
@Override
public ComputersArgumentType instantiate(@NotNull CommandBuildContext context) {
public ComputersArgumentType instantiate(CommandBuildContext context) {
return requireSome ? SOME : MANY;
}

View File

@@ -17,7 +17,6 @@ import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
@@ -144,7 +143,7 @@ public final class RepeatArgumentType<T, U> implements ArgumentType<List<T>> {
) implements ArgumentTypeInfo.Template<RepeatArgumentType<?, ?>> {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public RepeatArgumentType<?, ?> instantiate(@NotNull CommandBuildContext commandBuildContext) {
public RepeatArgumentType<?, ?> instantiate(CommandBuildContext commandBuildContext) {
var child = child().instantiate(commandBuildContext);
return flatten ? RepeatArgumentType.someFlat((ArgumentType) child, some()) : RepeatArgumentType.some(child, some());
}

View File

@@ -48,11 +48,15 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
return this;
}
public CommandBuilder<S> arg(String name, ArgumentType<?> type) {
args.add(RequiredArgumentBuilder.argument(name, type));
public CommandBuilder<S> arg(ArgumentBuilder<S, ?> arg) {
args.add(arg);
return this;
}
public CommandBuilder<S> arg(String name, ArgumentType<?> type) {
return arg(RequiredArgumentBuilder.argument(name, type));
}
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue(String name, ArgumentType<T> type, List<T> empty) {
return argMany(name, type, () -> empty);
}
@@ -74,7 +78,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
return command -> {
// The node for no arguments
var tail = tail(ctx -> command.run(ctx, empty.get()));
var tail = setupTail(ctx -> command.run(ctx, empty.get()));
// The node for one or more arguments
ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder
@@ -83,7 +87,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
// Chain all of them together!
tail.then(moreArg);
return link(tail);
return buildTail(tail);
};
}
@@ -94,20 +98,16 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
@Override
public CommandNode<S> executes(Command<S> command) {
if (args.isEmpty()) throw new IllegalStateException("Cannot have empty arg chain builder");
return link(tail(command));
return buildTail(setupTail(command));
}
private ArgumentBuilder<S, ?> tail(Command<S> command) {
var defaultTail = args.get(args.size() - 1);
defaultTail.executes(command);
if (requires != null) defaultTail.requires(requires);
return defaultTail;
private ArgumentBuilder<S, ?> setupTail(Command<S> command) {
return args.get(args.size() - 1).executes(command);
}
private CommandNode<S> link(ArgumentBuilder<S, ?> tail) {
private CommandNode<S> buildTail(ArgumentBuilder<S, ?> tail) {
for (var i = args.size() - 2; i >= 0; i--) tail = args.get(i).then(tail);
if (requires != null) tail.requires(requires);
return tail.build();
}
}

View File

@@ -10,6 +10,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import dan200.computercraft.shared.command.UserLevel;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.ClickEvent;
@@ -18,10 +19,11 @@ import net.minecraft.network.chat.Component;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static dan200.computercraft.core.util.Nullability.assertNonNull;
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
import static dan200.computercraft.shared.command.text.ChatHelpers.translate;
/**
* An alternative to {@link LiteralArgumentBuilder} which also provides a {@code /... help} command, and defaults
@@ -38,6 +40,29 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
return new HelpingArgumentBuilder(literal);
}
@Override
public LiteralArgumentBuilder<CommandSourceStack> requires(Predicate<CommandSourceStack> requirement) {
throw new IllegalStateException("Cannot use requires on a HelpingArgumentBuilder");
}
@Override
public Predicate<CommandSourceStack> getRequirement() {
// The requirement of this node is the union of all child's requirements.
var requirements = Stream.concat(
children.stream().map(ArgumentBuilder::getRequirement),
getArguments().stream().map(CommandNode::getRequirement)
).toList();
// If all requirements are a UserLevel, take the union of those instead.
var userLevel = UserLevel.OWNER;
for (var requirement : requirements) {
if (!(requirement instanceof UserLevel level)) return x -> requirements.stream().anyMatch(y -> y.test(x));
userLevel = UserLevel.union(userLevel, level);
}
return userLevel;
}
@Override
public LiteralArgumentBuilder<CommandSourceStack> executes(final Command<CommandSourceStack> command) {
throw new IllegalStateException("Cannot use executes on a HelpingArgumentBuilder");
@@ -77,13 +102,11 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
private LiteralCommandNode<CommandSourceStack> buildImpl(String id, String command) {
var helpCommand = new HelpCommand(id, command);
var node = new LiteralCommandNode<CommandSourceStack>(getLiteral(), helpCommand, getRequirement(), getRedirect(), getRedirectModifier(), isFork());
var node = new LiteralCommandNode<>(getLiteral(), helpCommand, getRequirement(), getRedirect(), getRedirectModifier(), isFork());
helpCommand.node = node;
// Set up a /... help command
var helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal("help")
.requires(x -> getArguments().stream().anyMatch(y -> y.getRequirement().test(x)))
.executes(helpCommand);
var helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal("help").executes(helpCommand);
// Add all normal command children to this and the help node
for (var child : getArguments()) {
@@ -153,9 +176,9 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
var output = Component.literal("")
.append(coloured("/" + command + usage, HEADER))
.append(" ")
.append(coloured(translate("commands." + id + ".synopsis"), SYNOPSIS))
.append(Component.translatable("commands." + id + ".synopsis").withStyle(SYNOPSIS))
.append("\n")
.append(translate("commands." + id + ".desc"));
.append(Component.translatable("commands." + id + ".desc"));
for (var child : node.getChildren()) {
if (!child.getRequirement().test(context.getSource()) || !(child instanceof LiteralCommandNode)) {
@@ -171,7 +194,7 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
));
output.append(component);
output.append(" - ").append(translate("commands." + id + "." + child.getName() + ".synopsis"));
output.append(" - ").append(Component.translatable("commands." + id + "." + child.getName() + ".synopsis"));
}
return output;

View File

@@ -23,28 +23,15 @@ public final class ChatHelpers {
}
public static MutableComponent coloured(@Nullable String text, ChatFormatting colour) {
return Component.literal(text == null ? "" : text).withStyle(colour);
}
public static <T extends MutableComponent> T coloured(T component, ChatFormatting colour) {
component.withStyle(colour);
return component;
return text(text).withStyle(colour);
}
public static MutableComponent text(@Nullable String text) {
return Component.literal(text == null ? "" : text);
}
public static MutableComponent translate(@Nullable String text) {
return Component.translatable(text == null ? "" : text);
}
public static MutableComponent translate(@Nullable String text, Object... args) {
return Component.translatable(text == null ? "" : text, args);
}
public static MutableComponent list(Component... children) {
var component = Component.literal("");
var component = Component.empty();
for (var child : children) {
component.append(child);
}
@@ -52,14 +39,14 @@ public final class ChatHelpers {
}
public static MutableComponent position(@Nullable BlockPos pos) {
if (pos == null) return translate("commands.computercraft.generic.no_position");
return translate("commands.computercraft.generic.position", pos.getX(), pos.getY(), pos.getZ());
if (pos == null) return Component.translatable("commands.computercraft.generic.no_position");
return Component.translatable("commands.computercraft.generic.position", pos.getX(), pos.getY(), pos.getZ());
}
public static MutableComponent bool(boolean value) {
return value
? coloured(translate("commands.computercraft.generic.yes"), ChatFormatting.GREEN)
: coloured(translate("commands.computercraft.generic.no"), ChatFormatting.RED);
? Component.translatable("commands.computercraft.generic.yes").withStyle(ChatFormatting.GREEN)
: Component.translatable("commands.computercraft.generic.no").withStyle(ChatFormatting.RED);
}
public static Component link(MutableComponent component, String command, Component toolTip) {
@@ -81,10 +68,9 @@ public final class ChatHelpers {
}
public static MutableComponent copy(String text) {
var name = Component.literal(text);
var style = name.getStyle()
return Component.literal(text).withStyle(s -> s
.withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, text))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("gui.computercraft.tooltip.copy")));
return name.withStyle(style);
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("gui.computercraft.tooltip.copy")))
);
}
}

View File

@@ -11,7 +11,6 @@ import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nullable;
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
import static dan200.computercraft.shared.command.text.ChatHelpers.translate;
public interface TableFormatter {
Component SEPARATOR = coloured("| ", ChatFormatting.GRAY);
@@ -99,7 +98,7 @@ public interface TableFormatter {
}
if (table.getAdditional() > 0) {
writeLine(id, coloured(translate("commands.computercraft.generic.additional_rows", table.getAdditional()), ChatFormatting.AQUA));
writeLine(id, Component.translatable("commands.computercraft.generic.additional_rows", table.getAdditional()).withStyle(ChatFormatting.AQUA));
}
}
}

View File

@@ -130,6 +130,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
@Override
public void playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
super.playerWillDestroy(world, pos, state, player);
if (!(world instanceof ServerLevel serverWorld)) return;
// We drop the item here instead of doing it in the harvest method, as we should

View File

@@ -199,6 +199,11 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
return localSide;
}
private void updateRedstoneInputs(ServerComputer computer) {
var pos = getBlockPos();
for (var dir : DirectionUtil.FACINGS) updateRedstoneInput(computer, dir, pos.relative(dir));
}
private void updateRedstoneInput(ServerComputer computer, Direction dir, BlockPos targetPos) {
var offsetSide = dir.getOpposite();
var localDir = remapToLocalSide(dir);
@@ -254,8 +259,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
// If the position is not any adjacent one, update all inputs. This is pretty terrible, but some redstone mods
// handle this incorrectly.
var pos = getBlockPos();
for (var dir : DirectionUtil.FACINGS) updateRedstoneInput(computer, dir, pos.relative(dir));
updateRedstoneInputs(computer);
invalidSides = (1 << 6) - 1; // Mark all peripherals as dirty.
}
@@ -264,9 +268,10 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
*/
public void updateOutput() {
BlockEntityHelpers.updateBlock(this);
for (var dir : DirectionUtil.FACINGS) {
RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir);
}
for (var dir : DirectionUtil.FACINGS) RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir);
var computer = getServerComputer();
if (computer != null) updateRedstoneInputs(computer);
}
protected abstract ServerComputer createComputer(int id);

View File

@@ -9,12 +9,16 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.core.ComputerContext;
import dan200.computercraft.core.computer.ComputerThread;
import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.core.computer.mainthread.MainThread;
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.methods.MethodSupplier;
import dan200.computercraft.core.methods.PeripheralMethod;
import dan200.computercraft.impl.AbstractComputerCraftAPI;
import dan200.computercraft.impl.ApiFactories;
import dan200.computercraft.impl.GenericSources;
import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
import dan200.computercraft.shared.config.ConfigSpec;
@@ -65,12 +69,14 @@ public final class ServerContext {
private ServerContext(MinecraftServer server) {
this.server = server;
storageDir = server.getWorldPath(FOLDER);
mainThread = new MainThread();
context = new ComputerContext(
new Environment(server),
new ComputerThread(ConfigSpec.computerThreads.get()),
mainThread, luaMachine
);
mainThread = new MainThread(mainThreadConfig);
context = ComputerContext.builder(new Environment(server))
.computerThreads(ConfigSpec.computerThreads.get())
.mainThreadScheduler(mainThread)
.luaFactory(luaMachine)
.apiFactories(ApiFactories.getAll())
.genericMethods(GenericSources.getAllMethods())
.build();
idAssigner = new IDAssigner(storageDir.resolve("ids.json"));
}
@@ -132,6 +138,16 @@ public final class ServerContext {
return context;
}
/**
* Get the {@link MethodSupplier} used to find methods on peripherals.
*
* @return The {@link PeripheralMethod} method supplier.
* @see ComputerContext#peripheralMethods()
*/
public MethodSupplier<PeripheralMethod> peripheralMethods() {
return context.peripheralMethods();
}
/**
* Tick all components of this server context. This should <em>NOT</em> be called outside of {@link CommonHooks}.
*/
@@ -215,4 +231,16 @@ public final class ServerContext {
return ComputerCraftAPI.MOD_ID + "/" + ComputerCraftAPI.getInstalledVersion();
}
}
private static final MainThreadConfig mainThreadConfig = new MainThreadConfig() {
@Override
public long maxGlobalTime() {
return TimeUnit.MILLISECONDS.toNanos(ConfigSpec.maxMainGlobalTime.get());
}
@Override
public long maxComputerTime() {
return TimeUnit.MILLISECONDS.toNanos(ConfigSpec.maxMainComputerTime.get());
}
};
}

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