From 9fe6fc433f382e6aed3c22f1a6f32d500d007225 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Mon, 4 Dec 2023 22:04:37 +0100 Subject: [PATCH] 20.3 update and cap rework --- content/news/20.3capability-rework.md | 408 ++++++++++++++++++++++++++ content/news/20.3release.md | 92 ++++++ 2 files changed, 500 insertions(+) create mode 100644 content/news/20.3capability-rework.md create mode 100644 content/news/20.3release.md diff --git a/content/news/20.3capability-rework.md b/content/news/20.3capability-rework.md new file mode 100644 index 0000000..0da1d91 --- /dev/null +++ b/content/news/20.3capability-rework.md @@ -0,0 +1,408 @@ +--- +title: "The Capability rework" +date: 2023-12-04T19:00:00+01:00 # TODO: change time to be after the 20.3 release post +categories: +- News +author: technici4n +summary: | + This post gives an overview of the changes made to the capability system of NeoForge 20.3. +description: | + This post gives an overview of the changes made to the capability system of NeoForge 20.3. +--- + +# Introduction +Our [initial 20.3 release](../20.3release/) comes with a fundamental redesign of the capability system, +with the goal of fixing all the issues that were found in the previous iteration after years of usage. + +Most importantly, there are now two different systems to replace what was previously known as "capabilities": +- **Data attachments** allow adding arbitrary **data** to block entities, chunks, entities, and item stacks. +- **Capabilities** allow querying **behavior** instances from blocks, entities, and item stacks. + +# Data attachments +The attachment system allows mods to attach arbitrary data objects to block entities, chunks, entities, and stacks. + +To use the system, you need to register an `AttachmentType`. +The attachment type contains: +- a default value supplier to create the instance when the data is first accessed, or to compare stacks that have the data and stacks that don't have it; +- an optional serializer if the attachment should be persisted; +- additional configuration options for the attachment, for example the `copyOnDeath` flag. + +There are a few ways to provide an attachment serializer: directly implementing `IAttachmentSerializer`, implementing `INBTSerializable` and using the static `AttachmentSerializer.serializable()` method to create the builder, or providing a codec to the builder. (This latter option is not recommended for item stacks due to relatively slowness). + +In any case, we recommend using a `DeferredRegister` for registration: +```java +// Create the DeferredRegister for attachment types +private static final DeferredRegister> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.ATTACHMENT_TYPES, MOD_ID); + +// Serialization via INBTSerializable +private static final Supplier> HANDLER = ATTACHMENT_TYPES.register( + "handler", () -> AttachmentType.serializable(() -> new ItemStackHandler(1)).build()); +// Serialization via codec +private static final Supplier> MANA = ATTACHMENT_TYPES.register( + "mana", () -> AttachmentType.builder(() -> 0).serialize(Codec.INT).build()); +// No serialization +private static final Supplier> SOME_CACHE = ATTACHMENT_TYPES.register( + "some_cache", () -> AttachmentType.builder(() -> new SomeCache()).build() +); + +// Don't forget to register the DeferredRegister to your mod bus: +ATTACHMENT_TYPES.register(modBus); +``` + +Once the attachment type is registered, it can be used on any holder object. +Calling `getData` if no data is present will attach a new default instance. + +```java +// Get the ItemStackHandler if it already exists, else attach a new one: +ItemStackHandler stackHandler = stack.getData(HANDLER); +// Get the current player mana if it is available, else attach 0: +int playerMana = player.getData(MANA); +// And so on... +``` + +If attaching a default instance is not desired, a `hasData` check can be added: +```java +// Check if the stack has the HANDLER attachment before doing anything. +if (stack.hasData(HANDLER)) { + ItemStackHandler stackHandler = stack.getData(HANDLER); + // Do something with stack.getData(HANDLER). +} +``` + +The data can also be updated with `setData`: +```java +// Increment mana by 10. +player.setData(MANA, player.getData(MANA) + 10); +``` + +Usually, block entities and chunks need to be marked as dirty when they are modified (with `setChanged` and `setUnsaved(true)`). This is done automatically for calls to `setData`: +```java +chunk.setData(MANA, chunk.getData(MANA) + 10); // will call setUnsaved automatically +``` +but if you modify some data that you obtained from `getData` then you must mark block entities and chunks as dirty explicitly: +```java +var mana = chunk.getData(MUTABLE_MANA); +mana.set(10); +chunk.setUnsaved(true); // must be done manually because we did not use setData +``` + +Before we move on to capabilities, here are a few points to take note of with respect to the data attachment system: +- **Level attachments were removed**: please use SavedData instead. +- Item stack attachments are always synced with the client now. +- Entity attachments are copied when a player is teleported back from the end. (Previously this was not the case). +- Entity attachments that have `copyOnDeath` set in their builder will automatically be copied on player death (and on mob conversion). + +### Future work for attachments +We have plans to work on the following improvements to the attachment system over the coming weeks: +- **Attachments in recipe JSONs**: Just like we add support for count and NBT to recipe results, we will add support to specify data attachments in recipe result JSONs. +- **Syncable data attachments**: Currently, all item stack attachments are synced automatically from the logical server to the logical client. +We will look into opt-in syncing for block entity, chunk, and entity attachments in the future. +- **Custom copy handler**: Currently, all data attachments are copied by serializing to NBT and then deserializing a new copy. +This is a good default, but we want to allow modders to provide their own copy implementation for better performance. + +We are open to other suggestions as well, don't hesitate to get in touch! + +# Capabilities +Capabilities are designed to separate **what** a block, entity or item stack can do from **how** it does it. +If you are wondering whether capabilities are the right tool for a job, ask yourself the following questions: +1. Do I only care about **what** a block, entity or item stack can do, but not about **how** it does it? +2. Is the **what**, the behavior only available for some blocks, entities, or item stacks, but not all of them? +3. Is the **how**, the implementation of that behavior, dependent on the specific block, entity or item stack? + +Here are a few examples of good capability usage: +- *"I want to count how many items are in some entity, but I do not know how the entity might store them."* - Yes, use the `IItemHandler` capability. +- *"I want to fill some item stack with power, but I do not know how the item stack might store it."* - Yes, use the `IEnergyStorage` capability. +- *"I want to apply some color to whatever block a player is currently targeting, but I do not know how the block will be transformed"*. - Yes. NeoForge does not provide a capability to color blocks, but you can implement one yourself. + +Here is an example of discouraged capability usage: +- *"I want to check if an entity is within the range of my machine."* - No, use a helper method instead. + +NeoForge supports capabilities for blocks, entities, and item stacks. + +Capabilities allow looking up implementations of some APIs with some dispatching logic. The following kinds of capabilities are implemented in NeoForge: +- `BlockCapability`: capabilities for blocks and block entities; behavior depends on the specific `Block`. +- `EntityCapability`: capabilities for entities: behavior dependends on the specific `EntityType`. +- `ItemCapability`: capabilities for item stacks: behavior depends on the specific `Item`. + +### Creating the capabilities +Creating a capability is a single function call. The following parameters must be provided: +- The name of the capability. Creating a capability with the same name multiple times will always return the same object. +- The behavior type that is being queried. This is the `T` type parameter. +- The type for additional context in the query. This is the `C` type parameter. + +For example, here is how a capability for side-aware block `IItemHandler`s might be declared: + +```java +public static final BlockCapability ITEM_HANDLER_BLOCK = + BlockCapability.create( + // Provide a name to uniquely identify the capability. + new ResourceLocation("mymod", "item_handler"), + // Provide the queried type. Here, we want to look up `IItemHandler` instances. + IItemHandler.class, + // Provide the context type. We will allow the query to receive an extra `Direction side` parameter. + Direction.class); +``` + +A `@Nullable Direction` is so common for blocks that there is a dedicated helper: +```java +public static final BlockCapability ITEM_HANDLER_BLOCK = + BlockCapability.createSided( + // Provide a name to uniquely identify the capability. + new ResourceLocation("mymod", "item_handler"), + // Provide the queried type. Here, we want to look up `IItemHandler` instances. + IItemHandler.class); +``` + +If no context is required, `Void` should be used. +There is also a dedicated helper for context-less capabilities: +```java +public static final BlockCapability ITEM_HANDLER_NO_CONTEXT = + BlockCapability.createVoid( + // Provide a name to uniquely identify the capability. + new ResourceLocation("mymod", "item_handler_no_context"), + // Provide the queried type. Here, we want to look up `IItemHandler` instances. + IItemHandler.class); +``` + +For entities and item stacks, similar methods exist in `EntityCapability` and `ItemCapability` respectively. + +### Querying capabilities +Once we have our `BlockCapability`, `EntityCapability`, or `ItemCapability` object in a static field, we can query a capability. + +Entities and item stacks have essentially the same API as before, but with a `@Nullable T` return type instead of `LazyOptional`. +Simply call `getCapability` with the capability object and the context: +```java +var object = entity.getCapability(CAP, context); +if (object != null) { + // Use object +} +``` +```java +var object = stack.getCapability(CAP, context); +if (object != null) { + // Use object +} +``` + +Block capabilities are used different, to accommodate for capabilities provided by blocks without block entities. +The query is performed on a `level`: +```java +var object = level.getCapability(CAP, pos, context); +if (object != null) { + // Use object +} +``` + +If the block entity and/or the block state is known, they can be passed to save on query time: +```java +var object = level.getCapability(CAP, pos, blockState, blockEntity, context); +if (object != null) { + // Use object +} +``` + +To give a more concrete example, here is how one might query an `IItemHandler` capability for a block, from the `Direction.NORTH` side: +```java +IItemHandler handler = level.getCapability(Capabilities.ItemHandler.BLOCK, pos, Direction.NORTH); +if (handler != null) { + // Use the handler for some item-related operation. +} +``` + +### Block capability caching +For efficient queries and automatic caching, use `BlockCapabilityCache`. +This is a more powerful replacement for the old `LazyOptional` invalidation system. + +When a capability is looked up, the system will perform the following steps under the hood: +1. Fetch block entity and block state if they were not supplied. +2. Fetch registered capability providers. (More on this below). +3. Iterate the providers and ask them if they can provide the capability. +4. One of the providers will return a capability instance, potentially allocating a new object. + +The implementation is rather efficient, but for queries that are performed frequently, +for example every game tick, these steps can take a significant amount of server time. +The `BlockCapabilityCache` system provides a dramatic speedup for capabilities that are frequently queried at a given position. + +Generally, a `BlockCapabilityCache` will be created once and then stored in a field. +The cache must be provided with the capability to query, the level, the position, and the query context. + +```java +// Declare the field: +private BlockCapabilityCache capCache; + +// Later, for example in `onLoad` for a block entity: +this.capCache = BlockCapabilityCache.create( + Capabilities.ItemHandler.BLOCK, // capability to cache + level, // level + pos, // target position + Direction.NORTH // context +); +``` + +Querying the cache is then done with `getCapability()`: +```java +IItemHandler handler = this.capCache.getCapability(); +if (handler != null) { + // Use the handler for some item-related operation. +} +``` + +**The cache is automatically cleared by the garbage collector, there is no need to unregister it.** + +It is also possible to receive notifications when the capability object changes! +This includes capabilities changing (`oldHandler != newHandler`), becoming unavailable (`null`) or becoming available again (not `null` anymore). + +The cache then needs to be created with two additional parameters: +- A validity check, that is used to determine if the cache is still valid. +In the simplest usage as a block entity field, `() -> !this.isRemoved()` will do. +- An invalidation listener, that is called when the capability changes. +This is where you can react to capability changes, removals, or appearances. + +```java +// With optional invalidation listener: +this.capCache = BlockCapabilityCache.create( + Capabilities.ItemHandler.BLOCK, // capability to cache + level, // level + pos, // target position + Direction.NORTH, // context + () -> !this.isRemoved(), // validity check (because the cache might outlive the object it belongs to) + () -> onCapInvalidate() // invalidation listener +); +``` + +For this system to work, **modders must call `level.invalidateCapabilities(pos)` whenever a capability changes, appears, or disappears**. +```java +// whenever a capability changes, appears, or disappears: +level.invalidateCapabilities(pos); +``` + +NeoForge already handles common cases such as chunk load/unloads and block entity creation/removal, +but other cases need to be handled explicitly by modders. +For example, modders must invalidate capabilities in the following cases: +- If the configuration of a capability-providing block entity changes. +- If a capability-providing block (without a block entity) is placed or changes state, by overriding `onPlace`. +- If a capability-providing block (without a block entity) is removed, by overriding `onRemove`. + +For a plain block example, refer to the `ComposterBlock.java` file. + +For more information, refer to the javadoc of `IBlockCapabilityProvider`. + +### Registering capabilities +A capability _provider_ is what ultimately supplies a capability. +A capability provider is function that can either return a capability instance, or `null` if it cannot provide the capability. +Providers are specific to: +- the given capability that they are providing for, and +- the block instance, block entity type, entity type, or item instance that they are providing for. + +They need to be registered in the `RegisterCapabilitiesEvent`. + +Block providers are registered with `registerBlock`. For example: +```java +private static void registerCapabilities(RegisterCapabilitiesEvent event) { + event.registerBlock( + Capabilities.ItemHandler.BLOCK, // capability to register for + (level, pos, state, be, side) -> , + // blocks to register for + MY_ITEM_HANDLER_BLOCK, + MY_OTHER_ITEM_HANDLER_BLOCK); +} +``` + +In general, registration will be specific to some block entity types, so the `registerBlockEntity` helper method is provided as well: +```java + event.registerBlockEntity( + Capabilities.ItemHandler.BLOCK, // capability to register for + MY_BLOCK_ENTITY_TYPE, // block entity type to register for + (myBlockEntity, side) -> ); +``` + +Entity registration is similar, using `registerEntity`: +```java +event.registerEntity( + Capabilities.ItemHandler.ENTITY, // capability to register for + MY_ENTITY_TYPE, // entity type to register for + (myEntity, context) -> ); +``` + +Item registration is similar too. Note that the provider receives the stack: +```java +event.registerItem( + Capabilities.ItemHandler.ITEM, // capability to register for + (itemStack, context) -> , + // items to register for + MY_ITEM, + MY_OTHER_ITEM); +``` + +If for some reason you need to register a provider for all blocks, entities, or items, +you will need to iterate the corresponding registry and register the provider for each object. + +For example, NeoForge uses this system to register a fluid handler capability for all buckets: +```java +// For reference, you can find this code in the `CapabilityHooks` class. +for (Item item : BuiltInRegistries.ITEM) { + if (item.getClass() == BucketItem.class) { + event.registerItem(Capabilities.FluidHandler.ITEM, (stack, ctx) -> new FluidBucketWrapper(stack), item); + } +} +``` + +Providers are asked for a capability in the order that they are registered. +Should you want to run before a provider that NeoForge already registers for one of your objects, +register your `RegisterCapabilitiesEvent` handler with a higher priority. +For example: +```java +modBus.addListener(RegisterCapabilitiesEvent.class, event -> { + event.registerItem( + Capabilities.FluidHandler.ITEM, + (stack, ctx) -> new MyCustomFluidBucketWrapper(stack), + // blocks to register for + MY_CUSTOM_BUCKET); +}, EventPriority.HIGH); // use HIGH priority to register before NeoForge! +``` +See `CapabilityHooks` for a list of the providers registered by NeoForge itself. + +### Entities, IItemHandler and Direction +_You can skip this section if you don't use the item handler entity capability._ + +There are now two capabilities for item handlers on entities: +- `Capabilities.ItemHandler.ENTITY`: exposes the **full inventory** of some entity. +- `Capabilities.ItemHandler.ENTITY_AUTOMATION`: exposes the **automation-accessible inventory**. Hoppers and droppers are patched to support that capability. + +Here is a migration guide from the old system that used a single capability, and distinguished using the `Direction` parameter: +#### Minecart and chest inventories +If you want to support automation-aware inventories: +| Old Syntax | New Syntax | +| -----------| ----------- | +| `entity.getCapability(...)` | `entity.getCapability(Capabilities.ItemHandler.ENTITY_AUTOMATION)` | + +Otherwise: +| Old Syntax | New Syntax | +| -----------| ----------- | +| `entity.getCapability(...)` | `entity.getCapability(Capabilities.ItemHandler.ENTITY)` | + +#### Horse inventory +| Old Syntax | New Syntax | +| -----------| ----------- | +| `horse.getCapability(..., ...)` | `horse.getCapability(Capabilities.ItemHandler.ENTITY)` | + +#### Living entities +| Old Syntax | New Syntax | +| -----------| ----------- | +| `entity.getCapability(..., any vertical direction)` | `new EntityHandsInvWrapper(livingEntity)` | +| `entity.getCapability(..., any horizontal direction)` | `new EntityArmorInvWrapper(livingEntity)` | +| `entity.getCapability(..., null)` | `livingEntity.getCapability(Capabilities.ItemHandler.ENTITY)` | + +#### Players +| Old Syntax | New Syntax | +| -----------| ----------- | +| `player.getCapability(..., any vertical direction)` | `new PlayerMainInvWrapper(player.getInventory())` | +| `player.getCapability(..., any horizontal direction)` | `new CombinedInvWrapper(new PlayerArmorInvWrapper(player.getInventory()), new PlayerOffhandInvWrapper(player.getInventory()))` | +| `player.getCapability(..., null)` | `player.getCapability(Capabilities.ItemHandler.ENTITY)` | + +### Future plans for capabilities +Composters NeoForge now support the item handler capability. However, cauldrons still do not support the fluid handler capability. This will be addressed in the coming weeks, and mods using the block fluid handler capability will work with cauldrons out of the box. + +We have reviewed and tested this capability overhaul extensively. Nonehtless, we expect that issues will be discovered after the release. Please don't hesitate to get in touch with us, be it on Discord or GitHub! + +That's all for now, happy porting! diff --git a/content/news/20.3release.md b/content/news/20.3release.md new file mode 100644 index 0000000..a55c33a --- /dev/null +++ b/content/news/20.3release.md @@ -0,0 +1,92 @@ +--- +title: "NeoForge 20.3 for Minecraft 1.20.3" +date: 2023-11-30T19:50:00+01:00 # TODO: update time +categories: +- News +author: neoforgedteam +summary: | + All you need to know about NeoForge 20.3, now released for Minecraft 1.20.3. +description: | + All you need to know about NeoForge 20.3, now released for Minecraft 1.20.3. +--- + +The first beta release of NeoForge for Minecraft 1.20.3, NeoForge 20.3.TODO-beta is now released! +Please try it out, play with it, develop with it, and give us feedback! +**We are not stable yet**, so expect a few breaking changes in the coming weeks. + +For players, you can grab the latest installer directly from https://neoforged.net/. + +The rest of this blog post is for modders. +Let's talk about recent updates to NeoForge that modders porting to 20.3 should be aware of. +Minecraft 1.20.3 itself also comes with a few technical changes, however that will not be covered in this post. + +**** + +## Capability rework +The most impactful change in 20.3 is the rework of the capability system. +You can read all about it in [our dedicated blog post](../20.3capability-rework/). + +## Noteworthy recent additions +Let's discuss a few changes that are already available in the 20.2 release series, +and of course in all 20.3 releases, +but you might not have heard about. + +### Access transformers inside `mods.toml` +Mods can now ship multiple access transformer files, by declaring them in their `mods.toml`: +```toml +[[accessTransformers]] +file="modid_base.at" + +[[accessTransformers]] +file="modid_extra.at" +``` +If no such entry is present, FML will fall back to `META-INF/accesstransformer.cfg` like before. +We will continue to accept both formats for the foreseeable future. + +Additionally, ATs still need to be specified for NeoGradle to apply them in your development environments: +```groovy +minecraft { + accessTransformers { + file('src/main/resources/modid_base.cfg') + file('src/main/resources/modid_extra.cfg') + } +} +``` + +### MixinExtras ships with NeoForge +MixinExtras by LlamaLad7 now ships with NeoForge and is enabled automatically. There is nothing required in your build.gradle with regards to MixinExtras anymore. + +MixinExtras is a completary library to Mixin, designed to help modders write their Mixins in a more expressive and compatible way. +If you use mixins in your mod, we encourage you to give the [MixinExtras wiki](https://github.com/LlamaLad7/MixinExtras/wiki) a read. + +If you want to override the bundled MixinExtras version with a newer one, you can do so by JiJing the newer version the same way you used to JiJ regular MixinExtras before this update. + +### Mixin configs inside `mods.toml` +Mixin configs can now be specified directly inside your `mods.toml` file, for example: +```toml +[[mixins]] +config="modid_base.mixins.json" + +[[mixins]] +config="modid_extra.mixins.json" +``` + +We recommend using this new format in your mods. +No Gradle support is needed anymore to use Mixins, +and this opens the door to selectively-enabled mixin configs in the future. + +## Coming Soon +Before we can stabilize 20.3, +we will be releasing an overhaul of our networking hooks and protocol to account for the configuration phase recently introduced by Mojang. +If you are interested in the discussion, we encourage you to join our Discord server and look at the `#brainstorming` forum channel. + +As usual, we also welcome all kinds of smaller contributions. +The breaking change window for 20.3 will remain open for a few weeks. +Now is a great time to start working on your Pull Requests. + +## 1.20.2 Plans +NeoForge for 1.20.2 is now stable, starting from version `20.2.86` released a few days ago. +Due to limited community adoption, +we will only accept backports for critical bugfixes, +provided that they are first submitted to and accepted for the 1.20.3 branch. +