useOn is now only responsible for firing the actual mod loader events,
and just returns the result of firing that event. The actual calling of
Block.use/Item.useOn now live in TurtlePlaceCommand.
This isn't especially useful for 1.20.1, but is more relevant on 1.21.1
when we look at #2011, as the shared code is much larger.
Okay, listen. I started writing a few more gametests (see #1682), and
then thought I'd do a cheeky Gradle update. However, that broke
vanilla-extract[^1], and also triggered a load of deprecation warnings,
and at that point it was too late to separate the too.
[^1]: 8975ed5a7b
MC 1.21.4 means we have to move more data generation code into the
client source set. Given all this code movement, it probably makes sense
to put data generation in a separate source set instead.
1.21.4 also has split data generators for client and server, but neither
mod loader recommends this. This means we can/should merge DataProviders
and ClientDataProviders into a single class.
Data generators are no longer bundled with the jar, which does reduce
file size, but by a tiny amount (~70KiB).
- Move redstone methods out of the IAPIEnvironment, and into a new
RedstoneAccess. We similarly move the implementation from Environment
into a new RedstoneState class.
The interface is possibly a little redundant (interfaces with a
single implementation are always a little suspect), but it's nice to
keep the consumer/producer interfaces separate.
- Abstract most redstone API methods into a separate shared class, that
can be used by both the rs API and the new redstone relay.
- Add the new redstone relay block.
The docs are probably a little lacking here, but I really struggled to
write anything which wasn't just "look, it's the same as the redstone
API".
- Place the player above the test region before running tests. This
guarantees the client has the chunks loaded (and rendered) before we
start running tests.
- Reset the time after running the monitor/printout tests.
- Fix rotation of turtle item models.
This still isn't perfect - the first test still fails with Iris and
Sodium - but is an improvement. Probably will still fail in CI though
:D:.
There's a whole load of gnarly issues that occur when a turtle is broken
mid-dig/attack (normally due to an explosion). We fixed most of these in
24af36743d08fcdb58439c52bf587b33ed828263, but not perfectly.
Part of the fix here was to not capture drops if the turtle BE has been
removed. However, on removal, turtles drop their items *before* removing
the BE. This meant that the drop consumer still triggered, and attempted
to insert items back into the turtle.
This bug only triggers if the turtle contains a stack larger than 10
(ish, I think) items, which is possibly why I'd never reproduced before.
We now drop items after removing the BE, which resolves the issue.
Fixes#1936.
There's been a couple of bug reports in the past where the game would
crash if a turtle is destroyed while breaking a block (typically due to
the block exploding). This commit adds a test, to ensure that this is
handled gracefully.
I'm not entirely sure this is testing the right thing. Looking at the
issues in question, it doesn't look like I ever managed to reproduce the
bug. However, it's hopefully at least a quick sanity test to check we
never break this case.
Computer drops are currently[^1] implemented via a dynamic drop. To
support this, we need to inject the dynamic drop into the loot
parameters.
We currently do this by implementing our own drop logic in
playerWillDestroy[^2], manually creating the loot params and adding our
additional drop. However, if the item is dropped via some other method
(such as via explosions), we'll go through vanilla's drop logic and so
never add the dynamic drop!
The correct way to do this is to override getDrops to add the dynamic
drop instead. I don't know why we didn't always do this -- the code in
question was first written for MC 1.14[^3], when things were very
different.
[^1]: This is no longer the case on 1.21, where we can just copy
capabilities.
[^2]: We need to override vanilla's drop behaviour to ensure items are
dropped in creative mode.
[^3]: See 594bc4203c6470e624a5f5e5edb2436590d1706c. Which probably means
the bug has been around for 5 years :/.
Historically (and according to the docs) getAudioTitle returned "false"
when the drive was empty (or had invalid media), and "null" when the
disk had no item. This was accidentally changed in a later refactor --
this change fixes that behaviour.
Rather than handling right clicks within the block entity code, we now
handle it within the block. Turtles now handle the nametagging
behaviour themselves, rather than overriding canNameWithTag.
This fixes several issues with @Nullable fields not being checked. This
is great in principle, but a little annoying in practice as MC's
@Nullable annotations are sometimes a little overly strict -- we now
need to wrap a couple of things in assertNonNull checks.
When the terminal data is not present, width/height are set to 0, rather
than the terminal's width/height. This meant we'd create an empty
terminal, which then crashes when we try to render it.
We now make the terminal nullable and initialise it the first time we
receive the terminal data. To prevent future mistakes, we hide
width/height, and use TerminalState.create everywhere.
Fixes#1765
I have mixed feelings about speaker.playSound. On one hand, it's pretty
useful to be able to play any sound. On the other, it sometimes feels
... maybe a little too magic?
One particular thing I don't like is that it allows you to play
arbitrary records, which sidesteps both a vanilla mechanic (finding
record discs) and existing CC functionality (disk.playAudio). We now
prevent playing record tracks from the speaker.
Here's a fun bug you can try at home:
- Create a new world
- Spawn in a pocket computer, turn it on, and place it in a chest.
- Reload the world - the pocket computer in the chest should now be
off.
- Spawn in a new pocket computer, and turn it on. The computer in chest
will also appear to be on!
This bug has been present since pocket computers were added (27th March,
2024).
When a pocket computer is added to a player's inventory, it is assigned
a unique *per-session* "instance id" , which is used to find the
associated computer. Note the "per-session" there - these ids will be
reused if you reload the world (or restart the server).
In the above bug, we see the following:
- The first pocket computer is assigned an instance id of 0.
- After reloading, the second pocket computer is assigned an instance
id of 0.
- If the first pocket computer was in our inventory, it'd be ticked and
assigned a new instance id. However, because it's in an inventory, it
keeps its old one.
- Both computers look up their client-side computer state and get the
same value, meaning the first pocket computer mirrors the second!
To fix this, we now ensure instance ids are entirely unique (not just
per-session). Rather than sequentially assigning an int, we now use a
random UUID (we probably could get away with a random long, but this
feels more idiomatic).
This has a couple of user-visible changes:
- /computercraft no longer lists instance ids outside of dumping an
individual computer.
- The @c[instance=...] selector uses UUIDs. We still use int instance
ids for the legacy selector, but that'll be removed in a later MC
version.
- Pocket computers now store a UUID rather than an int.
Related to this change (I made this change first, but then they got
kinda mixed up together), we now only create PocketComputerData when
receiving server data. This makes the code a little uglier in some
places (the data may now be null), but means we don't populate the
client-side pocket computer map with computers the server doesn't know
about.
- Remove "initial connections" flag, and just refresh connections +
peripherals on the first tick.
- Remove "peripheral attached" from NBT, and just read/write it from
the block state. This might cause issues with #1010, but that's
sufficiently old I hope it won't!
Our GatedPredicate hack was clever, but also fundamentally didn't work.
The predicate is called before extraction, so if extraction fails (for
instance, canTakeItemThroughFace returns false), then we still think an
item has been removed.
To fix that, we inline StorageUtil.move, specialising it for what we
need.
Disk drives have had a long-standing issue with mutating their contents
on the computer thread, potentially leading to all sorts of odd bugs.
We tried to fix this by moving setDiskLabel and the mounting code to run
on the main thread. Unfortunately, this means there is a slight delay to
mounts being attached, breaking disk startup.
This commit implements an alternative solution - we now do mounting on
the computer thread again. If the disk's stack is modified, we update it
in the peripheral-facing item, but not the actual inventory. The next
time the disk drive is ticked, we then sync the two items.
This does mean that there is a fraction of a tick where the two will be
out-of-sync. This isn't ideal - it would potentially be possible to
cycle through disk ids - but I don't really think that's avoidable
without significantly complicating the IMedia API.
Fixes#1649, fixes#1686.
- Add support for version overrides/exclusions in our dependency check.
Sometimes mod loaders use different versions to vanilla, and we need
some way to handle that.
- Rescan wired network connections on the tick after invalidation,
rather than when invalidated.
- Convert some constant lambdas to static method references. Lambdas
don't allocate if they don't capture variables, so this has the same
performance and is a little less ugly.
- Small code-style/formatting changes.
After embarrassing, let's do some proper work.
Rather than passing the level and position each time we call
ComponentAccess.get(), we now pass them at construction time (in the
form of the BE). This makes the consuming code a little cleaner, and is
required for the NeoForge changes in 1.20.4.
The two implementations aren't entirely compatible - the implementation
returned by .of will throw an NPE on .contains(null), whereas the
Collections implementations just return false. However, we try to avoid
passing null to collections methods, so this should be safe.
There's no strong reason to do this, but it helps make the code a little
more consistent
As of 1.20, sign messages are immutable - we need to do
text = text.setMesssage(...) instead. Also do a tiny bit of cleanup to
this function while we're here.
Probably not the best use of my lunch break :D:.
Fixes#1611.
Wow, some of this is /old/. All the Maps.newHashMap stuff dates back to
Java 6, so must originally be CCTweaks code?!
We're unlikely to drop our Guava dependency (we use too much other
stuff), but we should make the most of the stdlib where possible.
Previously we had the invariant that if we had a server monitor, we also
had a terminal. When a monitor shrank into a place, we deleted the
monitor, and then recreated it when a peripheral was requested.
As of ab785a090698e6972caac95691393428c6f8370b this has changed
slightly, and we now just delete the terminal (keeping the ServerMonitor
around). However, we didn't adjust the peripheral code accordingly,
meaning we didn't recreate the /terminal/ when a peripheral was
requested.
The fix for this is very simple - most of the rest of this commit is
some additional code for ensuring monitor invariants hold, so we can
write tests with a little more confidence.
I'm not 100% sold on this approach. It's tricky having a double layer of
nullable state (ServerMonitor, and then the terminal). However, I think
this is reasonable - the ServerMonitor is a reference to the multiblock,
and the Terminal is part of the multiblock's state.
Even after all the refactors, monitor code is still nastier than I'd
like :/.
Fixes#1608
This attempts to reduce some duplication in recipe serialisation (and
deserialisation) by moving the structure of a recipe (group, category,
ingredients, result) into seprate types.
- Add ShapedRecipeSpec and ShapelessRecipeSpec, which store the core
properties of shaped and shapeless recipes. There's a couple of
additional classes here for handling some of the other shared or
complex logic.
- These classes are now used by two new Custom{Shaped,Shapeless}Recipe
classes, which are (mostly) equivalent to Minecraft's
shaped/shapeless recipes, just with support for nbt in results.
- All the other similar recipes now inherit from these base classes,
which allows us to reuse a lot of this serialisation code. Alas, the
total code size has still gone up - maybe there's too much
abstraction here :).
- Mostly unrelated, but fix the skull recipes using the wrong UUID
format.
This allows us to remove our mixin for nbt in recipes (as we just use
our custom recipe now) and simplify serialisation a bit - hopefully
making the switch to codecs a little easier.
- 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.
- 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.
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.
- Use GuiGraphics for rendering UI elements. Almost definitely some
z-fighting issues slipped in here.
- Use Forge's loot modifier system for handling treasure disks. I have
mixed feelings about this - it's a nice system, but also is far less
efficient than the previous approach.
- Regenerate data. This is the brunt of the commit, but nothing
especially interesting here.
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