diff --git a/docs/concepts/events.md b/docs/concepts/events.md index 6f9038739..148eee0d0 100644 --- a/docs/concepts/events.md +++ b/docs/concepts/events.md @@ -27,13 +27,13 @@ private void modEventHandler(RegisterEvent event) { } // This event is on the forge bus -private static void forgeEventHandler(AttachCapabilitiesEvent event) { +private static void forgeEventHandler(ExplosionEvent.Detonate event) { // ... } // In the mod constructor modEventBus.addListener(this::modEventHandler); -forgeEventBus.addGenericListener(Entity.class, ExampleMod::forgeEventHandler); +forgeEventBus.addListener(ExampleMod::forgeEventHandler); ``` ### Instance Annotated Event Handlers diff --git a/docs/concepts/lifecycle.md b/docs/concepts/lifecycle.md index 99e66f2fd..a5b5090ed 100644 --- a/docs/concepts/lifecycle.md +++ b/docs/concepts/lifecycle.md @@ -47,7 +47,9 @@ If the game is setup to run [data generators][datagen], then the `GatherDataEven Common Setup ------------ -`FMLCommonSetupEvent` is for actions that are common to both physical client and server, such as registering [capabilities][capabilities]. +`FMLCommonSetupEvent` is for actions that are common to both physical client and server. +This event is fired for multiple mods in parallel, so be careful. +You can use `event.enqueueWork(() -> /* do something */)` to run code that is not thread-safe. Sided Setup ----------- @@ -70,7 +72,6 @@ There are two other lifecycle events: `FMLConstructModEvent`, fired directly aft ::: [registering]: ./registries.md#methods-for-registering -[capabilities]: ../datastorage/capabilities.md [datagen]: ../datagen/index.md [imc]: ./lifecycle.md#intermodcomms [sides]: ./sides.md diff --git a/docs/datastorage/attachments.md b/docs/datastorage/attachments.md new file mode 100644 index 000000000..6f2aace5a --- /dev/null +++ b/docs/datastorage/attachments.md @@ -0,0 +1,109 @@ +# Data Attachments + +The data attachment system allows mods to attach and store additional data on block entities, chunks, entities, and item stacks. + +_To store additional level data, you can use [SavedData](saveddata)._ + +## Creating an attachment type + +To use the system, you need to register an `AttachmentType`. +The attachment type contains the following configuration: +- A default value supplier to create the instance when the data is first accessed. Also used to compare stacks that have the data with stacks that don't have it. +- An optional serializer if the attachment should be persisted. +- (If a serializer was configured) The `copyOnDeath` flag to automatically copy entity data on death (see below). +- (Advanced) (If a serializer was configured) A custom `comparator` to use when checking if the data is the same for two item stacks. + +:::tip +If you don't want your attachment to persist, do not provide a serializer. +::: + +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. + +:::warning +Avoid serialization with codecs for item stack attachments, as it is comparatively slow. +::: + +In any case, the attachment **must be registered** to the `NeoForgeRegistries.ATTACHMENT_TYPES` registry. Here is an example: +```java +// Create the DeferredRegister for attachment types +private static final DeferredRegister> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.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() +); + +// In your mod constructor, don't forget to register the DeferredRegister to your mod bus: +ATTACHMENT_TYPES.register(modBus); +``` + +## Using the attachment type + +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); +``` + +:::important +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` (including a newly created default instance) 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 +``` +::: + +## Sharing data with the client +Currently, only serializable item stack attachments are synced between the client and the server. +This is done automatically. + +To sync block entity, chunk, or entity attachments to a client, you need to [send a packet to the client][network] yourself. +For chunks, you can use `ChunkWatchEvent.Sent` to know when to send chunk data to a player. + +## Copying data on player death +By default, entity data attachments are not copied on player death. +To automatically copy an attachment on player death, set `.copyOnDeath()` in the attachment builder. + +More complex handling can be implemented via `PlayerEvent.Clone` by reading the data from the original entity and assigning it to the new entity. In this event, the `#isWasDeath` method can be used to distinguish between respawning after death and returning from the End. This is important because the data will already exist when returning from the End, so care has to be taken to not duplicate values in this case. + +For example: +```java +NeoForge.EVENT_BUS.register(PlayerEvent.Clone.class, event -> { + if (event.isWasDeath() && event.getOriginal().hasData(MY_DATA)) { + event.getEntity().getData(MY_DATA).fieldToCopy = event.getOriginal().getData(MY_DATA).fieldToCopy; + } +}); +``` + +[network]: ../networking/index.md \ No newline at end of file diff --git a/docs/datastorage/capabilities.md b/docs/datastorage/capabilities.md index 9204511d5..94fff8c71 100644 --- a/docs/datastorage/capabilities.md +++ b/docs/datastorage/capabilities.md @@ -1,177 +1,321 @@ -The Capability System -===================== +# Capabilities Capabilities allow exposing features in a dynamic and flexible way without having to resort to directly implementing many interfaces. In general terms, each capability provides a feature in the form of an interface. -Forge adds capability support to BlockEntities, Entities, ItemStacks, Levels, and LevelChunks, which can be exposed either by attaching them through an event or by overriding the capability methods in your own implementations of the objects. This will be explained in more detail in the following sections. +NeoForge adds capability support to blocks, entities, and item stacks. +This will be explained in more detail in the following sections. -Forge-provided Capabilities ---------------------------- +## Why use capabilities -Forge provides three capabilities: `IItemHandler`, `IFluidHandler` and `IEnergyStorage` +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? -`IItemHandler` exposes an interface for handling inventory slots. It can be applied to BlockEntities (chests, machines, etc.), Entities (extra player slots, mob/creature inventories/bags), or ItemStacks (portable backpacks and such). It replaces the old `Container` and `WorldlyContainer` with an automation-friendly system. +Here are a few examples of good capability usage: +- *"I want my fluid container to be compatible with fluid containers from other mods, but I don't know the specifics of each fluid container."* - Yes, use the `IFluidHandler` capability. +- *"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. -`IFluidHandler` exposes an interface for handling fluid inventories. It can also be applied to BlockEntities, Entities, or ItemStacks. +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. -`IEnergyStorage` exposes an interface for handling energy containers. It can be applied to BlockEntities, Entities, or ItemStacks. It is based on the RedstoneFlux API by TeamCoFH. +## NeoForge-provided capabilities -Using an Existing Capability ----------------------------- +NeoForge provides capabilities for the following three interfaces: `IItemHandler`, `IFluidHandler` and `IEnergyStorage`. -As mentioned earlier, BlockEntities, Entities, and ItemStacks implement the capability provider feature through the `ICapabilityProvider` interface. This interface adds the method `#getCapability`, which can be used to query the capabilities present in the associated provider objects. +`IItemHandler` exposes an interface for handling inventory slots. The capabilities of type `IItemHandler` are: +- `Capabilities.ItemHandler.BLOCK`: automation-accessible inventory of a block (for chests, machines, etc). +- `Capabilities.ItemHandler.ENTITY`: inventory contents of an entity (extra player slots, mob/creature inventories/bags). +- `Capabilities.ItemHandler.ENTITY_AUTOMATION`: automation-accessible inventory of an entity (boats, minecarts, etc). +- `Capabilities.ItemHandler.ITEM`: contents of an item stack (portable backpacks and such). -In order to obtain a capability, you will need to refer it by its unique instance. In the case of the `IItemHandler`, this capability is primarily stored in `ForgeCapabilities#ITEM_HANDLER`, but it is possible to get other instance references by using `CapabilityManager#get` +`IFluidHandler` exposes an interface for handling fluid inventories. The capabilities of type `IFluidHandler` are: +- `Capabilities.FluidHandler.BLOCK`: automation-accessible fluid inventory of a block. +- `Capabilities.FluidHandler.ENTITY`: fluid inventory of an entity. +- `Capabilities.FluidHandler.ITEM`: fluid inventory of an item stack. +This capability is of the special `IFluidHandlerItem` type due to the way buckets hold fluids. -```java -public static final Capability ITEM_HANDLER = CapabilityManager.get(new CapabilityToken<>(){}); -``` +`IEnergyStorage` exposes an interface for handling energy containers. It is based on the RedstoneFlux API by TeamCoFH. The capabilities of type `IEnergyStorage` are: +- `Capabilities.EnergyStorage.BLOCK`: energy contained inside a block. +- `Capabilities.EnergyStorage.ENTITY`: energy containing inside an entity. +- `Capabilities.EnergyStorage.ITEM`: energy contained inside an item stack. -When called, `CapabilityManager#get` provides a non-null capability for your associated type. The anonymous `CapabilityToken` allows Forge to keep a soft dependency system while still having the necessary generic information to get the correct capability. +## Creating a capability -:::danger -Even if you have a non-null capability available to you at all times, it does not mean the capability itself is usable or registered yet. This can be checked via `Capability#isRegistered`. -::: +NeoForge supports capabilities for blocks, entities, and item stacks. -The `#getCapability` method has a second parameter, of type `Direction`, which can be used to request the specific instance for that one face. If passed `null`, it can be assumed that the request comes either from within the block or from some place where the side has no meaning, such as a different dimension. In this case a general capability instance that does not care about sides will be requested instead. The return type of `#getCapability` will correspond to a `LazyOptional` of the type declared in the capability passed to the method. For the Item Handler capability, this is `LazyOptional`. If the capability is not available for a particular provider, it will return an empty `LazyOptional` instead. +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`. -Exposing a Capability ---------------------- +:::tip +For compatibility with other mods, +we recommend using the capabilities provided by NeoForge in the `Capabilities` class if possible. +Otherwise, you can create your own as described in this section. +::: -In order to expose a capability, you will first need an instance of the underlying capability type. Note that you should assign a separate instance to each object that keeps the capability, since the capability will most probably be tied to the containing object. +Creating a capability is a single function call, and the resulting object should be stored in a `static final` field. +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. +Capabilities with different names are **completely independent**, and can be used for different purposes. +- 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. -In the case of `IItemHandler`, the default implementation uses the `ItemStackHandler` class, which has an optional argument in the constructor, to specify a number of slots. However, relying on the existence of these default implementations should be avoided, as the purpose of the capability system is to prevent loading errors in contexts where the capability is not present, so instantiation should be protected behind a check testing if the capability has been registered (see the remarks about `CapabilityManager#get` in the previous section). +For example, here is how a capability for side-aware block `IItemHandler`s might be declared: -Once you have your own instance of the capability interface, you will want to notify users of the capability system that you expose this capability and provide a `LazyOptional` of the interface reference. This is done by overriding the `#getCapability` method, and comparing the capability instance with the capability you are exposing. If your machine has different slots based on which side is being queried, you can test this with the `side` parameter. For Entities and ItemStacks, this parameter can be ignored, but it is still possible to have side as a context, such as different armor slots on a player (`Direction#UP` exposing the player's helmet slot), or about the surrounding blocks in the inventory (`Direction#WEST` exposing the input slot of a furnace). Do not forget to fall back to `super`, otherwise existing attached capabilities will stop working. +```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); +``` -Capabilities must be invalidated at the end of the provider's lifecycle via `LazyOptional#invalidate`. For owned BlockEntities and Entities, the `LazyOptional` can be invalidated within `#invalidateCaps`. For non-owned providers, a runnable supplying the invalidation should be passed into `AttachCapabilitiesEvent#addListener`. +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 -// Somewhere in your BlockEntity subclass -LazyOptional inventoryHandlerLazyOptional; +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); +``` -// Supplied instance (e.g. () -> inventoryHandler) -// Ensure laziness as initialization should only happen when needed -inventoryHandlerLazyOptional = LazyOptional.of(inventoryHandlerSupplier); +For entities and item stacks, similar methods exist in `EntityCapability` and `ItemCapability` respectively. -@Override -public LazyOptional getCapability(Capability cap, Direction side) { - if (cap == ForgeCapabilities.ITEM_HANDLER) { - return inventoryHandlerLazyOptional.cast(); - } - return super.getCapability(cap, side); -} +## Querying capabilities +Once we have our `BlockCapability`, `EntityCapability`, or `ItemCapability` object in a static field, we can query a capability. + +For entities and item stacks, we can try to find implementations of a capability with `getCapability`. +If the result is `null`, there no implementation is available. -@Override -public void invalidateCaps() { - super.invalidateCaps(); - inventoryHandlerLazyOptional.invalidate(); +For example: +```java +var object = entity.getCapability(CAP, context); +if (object != null) { + // Use object } ``` - -:::tip -If only one capability is exposed on a given object, you can use `Capability#orEmpty` as an alternative to the if/else statement. - ```java -@Override -public LazyOptional getCapability(Capability cap, Direction side) { - return ForgeCapabilities.ITEM_HANDLER.orEmpty(cap, inventoryHandlerLazyOptional); +var object = stack.getCapability(CAP, context); +if (object != null) { + // Use object } ``` -::: -`Item`s are a special case since their capability providers are stored on an `ItemStack`. Instead, a provider should be attached through `Item#initCapabilities`. This should hold your capabilities for the lifecycle of the stack. +Block capabilities are used a bit differently because blocks without a block entity can have capabilities as well. +The query is now performed on a `level`, with the `pos`ition that we are looking for as an additional parameter: +```java +var object = level.getCapability(CAP, pos, context); +if (object != null) { + // Use object +} +``` -It is strongly suggested that direct checks in code are used to test for capabilities instead of attempting to rely on maps or other data structures, since capability tests can be done by many objects every tick, and they need to be as fast as possible in order to avoid slowing down the game. +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 +} +``` -Attaching Capabilities ----------------------- +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. +} +``` -As mentioned, attaching capabilities to existing providers, `Level`s, and `LevelChunk`s can be done using `AttachCapabilitiesEvent`. The same event is used for all objects that can provide capabilities. `AttachCapabilitiesEvent` has 5 valid generic types providing the following events: +## Block capability caching +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. -* `AttachCapabilitiesEvent`: Fires only for entities. -* `AttachCapabilitiesEvent`: Fires only for block entities. -* `AttachCapabilitiesEvent`: Fires only for item stacks. -* `AttachCapabilitiesEvent`: Fires only for levels. -* `AttachCapabilitiesEvent`: Fires only for level chunks. +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. -The generic type cannot be more specific than the above types. For example: If you want to attach capabilities to `Player`, you have to subscribe to the `AttachCapabilitiesEvent`, and then determine that the provided object is an `Player` before attaching the capability. +:::tip +Generally, a `BlockCapabilityCache` will be created once and then stored in a field of the object performing frequent capability queries. +When and where exactly you store the cache is up to you. +::: -In all cases, the event has a method `#addCapability` which can be used to attach capabilities to the target object. Instead of adding capabilities themselves to the list, you add capability providers, which have the chance to return capabilities only from certain sides. While the provider only needs to implement `ICapabilityProvider`, if the capability needs to store data persistently, it is possible to implement `ICapabilitySerializable` which, on top of returning the capabilities, will provide tag save/load functions. +To create a cache, call `BlockCapabilityCache.create` with the capability to query, the level, the position, and the query context. -For information on how to implement `ICapabilityProvider`, refer to the [Exposing a Capability][expose] section. +```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 +); +``` -Creating Your Own Capability ----------------------------- +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. +} +``` -A capability can be registered using one of two ways: `RegisterCapabilitiesEvent` or `@AutoRegisterCapability`. +**The cache is automatically cleared by the garbage collector, there is no need to unregister it.** -### RegisterCapabilitiesEvent +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). -A capability can be registered using `RegisterCapabilitiesEvent` by supplying the class of the capability type to the `#register` method. The event is [handled] on the mod event bus. +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 -@SubscribeEvent -public void registerCaps(RegisterCapabilitiesEvent event) { - event.register(IExampleCapability.class); -} +// 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 +); ``` -### @AutoRegisterCapability - -A capability is registered using `@AutoRegisterCapability` by annotating the capability type. +## Block capability invalidation +:::info +Invalidation is exclusive to block capabilities. Entity and item stack capabilities cannot be cached and do not need to be invalidated. +::: +To make sure that caches can correctly update their stored capability, **modders must call `level.invalidateCapabilities(pos)` whenever a capability changes, appears, or disappears**. ```java -@AutoRegisterCapability -public interface IExampleCapability { - // ... -} +// whenever a capability changes, appears, or disappears: +level.invalidateCapabilities(pos); ``` -Persisting LevelChunk and BlockEntity capabilities --------------------------------------------- +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 a previously returned capability is no longer valid. +- 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`. -Unlike Levels, Entities, and ItemStacks, LevelChunks and BlockEntities are only written to disk when they have been marked as dirty. A capability implementation with persistent state for a LevelChunk or a BlockEntity should therefore ensure that whenever its state changes, its owner is marked as dirty. +For a plain block example, refer to the `ComposterBlock.java` file. -`ItemStackHandler`, commonly used for inventories in BlockEntities, has an overridable method `void onContentsChanged(int slot)` designed to be used to mark the BlockEntity as dirty. +For more information, refer to the javadoc of [`IBlockCapabilityProvider`][block-cap-provider]. -```java -public class MyBlockEntity extends BlockEntity { +## 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. - private final IItemHandler inventory = new ItemStackHandler(...) { - @Override - protected void onContentsChanged(int slot) { - super.onContentsChanged(slot); - setChanged(); - } - } +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); } ``` -Synchronizing Data with Clients -------------------------------- +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) -> ); +``` -By default, capability data is not sent to clients. In order to change this, the mods have to manage their own synchronization code using packets. +:::danger +If the capability previously returned by a block or block entity provider is no longer valid, +**you must invalidate the caches** by calling `level.invalidateCapabilities(pos)`. +Refer to the [invalidation section][invalidation] above for more information. +::: -There are three different situations in which you may want to send synchronization packets, all of them optional: +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) -> ); +``` -1. When the entity spawns in the level, or the block is placed, you may want to share the initialization-assigned values with the clients. -2. When the stored data changes, you may want to notify some or all of the watching clients. -3. When a new client starts viewing the entity or block, you may want to notify it of the existing data. +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); +``` -Refer to the [Networking][network] page for more information on implementing network packets. +## Registering capabilities for all objects -Persisting across Player Deaths -------------------------------- +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. -By default, the capability data does not persist on death. In order to change this, the data has to be manually copied when the player entity is cloned during the respawn process. +For example, NeoForge uses this system to register a fluid handler capability for all `BucketItem`s (excluding subclasses): +```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); + } +} +``` -This can be done via `PlayerEvent$Clone` by reading the data from the original entity and assigning it to the new entity. In this event, the `#isWasDeath` method can be used to distinguish between respawning after death and returning from the End. This is important because the data will already exist when returning from the End, so care has to be taken to not duplicate values in this case. +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`][capability-hooks] for a list of the providers registered by NeoForge itself. -[expose]: #exposing-a-capability -[handled]: ../concepts/events.md#creating-an-event-handler -[network]: ../networking/index.md +[block-cap-provider]: https://github.com/neoforged/NeoForge/blob/1.20.x/src/main/java/net/neoforged/neoforge/capabilities/IBlockCapabilityProvider.java +[capability-hooks]: https://github.com/neoforged/NeoForge/blob/1.20.x/src/main/java/net/neoforged/neoforge/capabilities/CapabilityHooks.java +[invalidation]: #block-capability-invalidation diff --git a/docs/datastorage/saveddata.md b/docs/datastorage/saveddata.md index 7ab85faca..4534051c4 100644 --- a/docs/datastorage/saveddata.md +++ b/docs/datastorage/saveddata.md @@ -1,7 +1,9 @@ Saved Data ========== -The Saved Data (SD) system is an alternative to level capabilities that can attach data per level. +The Saved Data (SD) system can be used to save additional data on levels. + +_If the data is specific to some block entities, chunks, or entities, consider using a [data attachment](attachments) instead._ Declaration ----------- diff --git a/docs/gui/menus.md b/docs/gui/menus.md index 3a5f63c25..3744046cc 100644 --- a/docs/gui/menus.md +++ b/docs/gui/menus.md @@ -100,7 +100,7 @@ Some data needs to be present on both the server and the client to display to th Minecraft supports two forms of data synchronization by default: `ItemStack`s via `Slot`s and integers via `DataSlot`s. `Slot`s and `DataSlot`s are views which hold references to data storages that can be be modified by the player in a screen, assuming the action is valid. These can be added to a menu within the constructor through `#addSlot` and `#addDataSlot`. :::note -Since `Container`s used by `Slot`s are deprecated by Forge in favor of using the [`IItemHandler` capability][cap], the rest of the explanation will revolve around using the capability variant: `SlotItemHandler`. +Since `Container`s used by `Slot`s are deprecated by NeoForge in favor of using the [`IItemHandler` capability][cap], the rest of the explanation will revolve around using the capability variant: `SlotItemHandler`. ::: A `SlotItemHandler` contains four parameters: the `IItemHandler` representing the inventory the stacks are within, the index of the stack this slot is specifically representing, and the x and y position of where the top-left position of the slot will render on the screen relative to `AbstractContainerScreen#leftPos` and `#topPos`. The client menu constructor should always supply an empty instance of an inventory of the same size. @@ -342,6 +342,6 @@ Once again, this is the simplest way to implement the logic, not the only way. [acm]: #abstractcontainermenu [mt]: #menutype [qms]: #quickmovestack -[cap]: ../datastorage/capabilities.md#forge-provided-capabilities +[cap]: ../datastorage/capabilities.md#neoforge-provided-capabilities [screen]: ./screens.md [icf]: #icontainerfactory diff --git a/docs/resources/server/recipes/index.md b/docs/resources/server/recipes/index.md index d41b60501..cbca08029 100644 --- a/docs/resources/server/recipes/index.md +++ b/docs/resources/server/recipes/index.md @@ -100,6 +100,5 @@ A few additional [ingredient types][ingredients] are added to allow recipes to h [wiki]: https://minecraft.wiki/w/Recipe [advancement]: ../advancements.md [datagen]: ../../../datagen/server/recipes.md -[cap]: ../../../datastorage/capabilities.md [conditional]: ../conditional.md#implementations [ingredients]: ./ingredients.md#forge-types