能力

能力

能力(Capability)允许以动态、灵活的方式暴露功能,无需直接实现大量接口。概括来说,每种能力都以接口形式提供一项功能。

NeoForge 为方块、实体和物品堆提供能力支持,下文会分别介绍。

为什么使用能力?

能力将方块、实体或物品堆“能做什么”与“如何实现”分离。如果不确定某项工作是否适合使用能力,可以考虑以下问题:

  1. 我是否只关心它能做什么,而不关心如何实现?
  2. 这种行为是否只存在于部分方块、实体或物品堆,而非全部对象?
  3. 行为的实现方式是否取决于具体方块、实体或物品堆?

以下是适合使用能力的例子:

  • “我希望自己的流体容器兼容其他 Mod 的容器,但不知道它们的具体实现。”——使用 IFluidHandler 能力。
  • “我想统计某个实体包含多少物品,但不知道它如何存储物品。”——使用 IItemHandler 能力。
  • “我想给某个物品堆充能,但不知道它如何存储能量。”——使用 IEnergyStorage 能力。
  • “我想为玩家指向的任意方块着色,但不知道方块应如何变换。”——适合使用能力。NeoForge 没有内置方块着色能力,但可以自行实现。

以下情况不推荐使用能力:

  • “我想检查实体是否处于机器范围内。”——应使用辅助方法。

NeoForge 提供的能力

NeoForge 为 IItemHandler、IFluidHandler 和 IEnergyStorage 三种接口提供能力。

IItemHandler 暴露物品栏槽位处理接口,对应能力包括:

  • Capabilities.ItemHandler.BLOCK:可供自动化访问的方块物品栏,例如箱子、机器。
  • Capabilities.ItemHandler.ENTITY:实体物品栏内容,例如玩家额外槽位、生物物品栏或背包。
  • Capabilities.ItemHandler.ENTITY_AUTOMATION:可供自动化访问的实体物品栏,例如船和矿车。
  • Capabilities.ItemHandler.ITEM:物品堆内容,例如便携背包。

IFluidHandler 暴露流体存储处理接口,对应能力包括:

  • Capabilities.FluidHandler.BLOCK:可供自动化访问的方块流体存储。
  • Capabilities.FluidHandler.ENTITY:实体流体存储。
  • Capabilities.FluidHandler.ITEM:物品堆流体存储。由于桶存储流体的方式特殊,此能力使用专门的 IFluidHandlerItem 类型。

IEnergyStorage 暴露能量容器处理接口,基于 TeamCoFH 的 RedstoneFlux API。对应能力包括:

  • Capabilities.EnergyStorage.BLOCK:方块中的能量。
  • Capabilities.EnergyStorage.ENTITY:实体中的能量。
  • Capabilities.EnergyStorage.ITEM:物品堆中的能量。

创建能力

NeoForge 支持方块、实体和物品堆的能力。能力通过某种分派逻辑查找 API 的实现,具体有三类:

  • BlockCapability:方块和方块实体的能力,行为取决于具体 Block。
  • EntityCapability:实体能力,行为取决于具体 EntityType。
  • ItemCapability:物品堆能力,行为取决于具体 Item。

建议 为了兼容其他 Mod,应尽可能使用 Capabilities 中由 NeoForge 提供的能力。没有合适能力时,再按本节方式自行创建。

调用一个函数即可创建能力,结果应保存在 static final 字段中。必须提供以下参数:

  • 能力名称。
    • 多次用同一名称创建能力,始终返回同一对象。
    • 不同名称的能力完全独立,可用于不同用途。
  • 查询的行为类型,即类型参数 T。
  • 查询附加上下文的类型,即类型参数 C。

例如,可按如下方式声明区分方向的方块 IItemHandler 能力:

public static final BlockCapability<IItemHandler, @Nullable Direction> ITEM_HANDLER_BLOCK =
    BlockCapability.create(
        // Provide a name to uniquely identify the capability.
        ResourceLocation.fromNamespaceAndPath("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);

方块能力经常使用 @Nullable Direction,因此提供了专用辅助方法:

public static final BlockCapability<IItemHandler, @Nullable Direction> ITEM_HANDLER_BLOCK =
    BlockCapability.createSided(
        // Provide a name to uniquely identify the capability.
        ResourceLocation.fromNamespaceAndPath("mymod", "item_handler"),
        // Provide the queried type. Here, we want to look up `IItemHandler` instances.
        IItemHandler.class);

无需上下文时应使用 Void,也有专用辅助方法:

public static final BlockCapability<IItemHandler, Void> ITEM_HANDLER_NO_CONTEXT =
    BlockCapability.createVoid(
        // Provide a name to uniquely identify the capability.
        ResourceLocation.fromNamespaceAndPath("mymod", "item_handler_no_context"),
        // Provide the queried type. Here, we want to look up `IItemHandler` instances.
        IItemHandler.class);

实体与物品堆分别在 EntityCapability 和 ItemCapability 中提供类似方法。

查询能力

将 BlockCapability、EntityCapability 或 ItemCapability 保存到静态字段后,就可以查询能力。

对于实体和物品堆,调用 getCapability 尝试获取实现。若结果为 null,表示没有可用实现。例如:

var object = entity.getCapability(CAP, context);
if (object != null) {
    // Use object
}
var object = stack.getCapability(CAP, context);
if (object != null) {
    // Use object
}

方块能力略有不同,因为没有方块实体的方块也可以有能力。查询通过 level 执行,并额外传入目标位置 pos:

var object = level.getCapability(CAP, pos, context);
if (object != null) {
    // Use object
}

如果已经知道方块实体和/或方块状态,可以传入以节省查询时间:

var object = level.getCapability(CAP, pos, blockState, blockEntity, context);
if (object != null) {
    // Use object
}

下面是从 Direction.NORTH 方向查询方块 IItemHandler 能力的具体示例:

IItemHandler handler = level.getCapability(Capabilities.ItemHandler.BLOCK, pos, Direction.NORTH);
if (handler != null) {
    // Use the handler for some item-related operation.
}

方块能力缓存

查询能力时,系统内部会执行以下步骤:

  1. 如果未提供方块实体和方块状态,先获取它们。
  2. 获取已注册的能力提供器,下文会介绍。
  3. 遍历提供器,询问能否提供能力。
  4. 某个提供器返回能力实例,过程中可能分配新对象。

虽然实现比较高效,但如果每刻等高频查询,这些步骤仍可能占用大量服务器时间。BlockCapabilityCache 可以显著加速对固定位置的频繁查询。

建议 通常只创建一次 BlockCapabilityCache,并将其存储在高频查询能力的对象字段中。具体存储时机和位置由你决定。

调用 BlockCapabilityCache.create 创建缓存,传入能力、世界、位置和查询上下文。

// Declare the field:
private BlockCapabilityCache<IItemHandler, @Nullable Direction> 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
);

然后调用 getCapability() 查询缓存:

IItemHandler handler = this.capCache.getCapability();
if (handler != null) {
    // Use the handler for some item-related operation.
}

缓存会由垃圾回收器自动清理,无需注销。

能力对象变化时,也可以接收通知,包括对象变化(oldHandler != newHandler)、变为不可用(null)或重新可用(不再为 null)。此时创建缓存还需两个参数:

  • 有效性检查,判断缓存本身是否仍有效。
    • 最简单的方块实体字段用法中,() -> !this.isRemoved() 即可。
  • 失效监听器,在能力变化时调用。
    • 可以在此响应能力变更、移除或出现。
// 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
);

方块能力失效

说明 失效机制仅适用于方块能力。实体和物品堆能力不能缓存,也无需标记失效。

为了让缓存正确更新所保存的能力,每当能力变化、出现或消失时,Mod 开发者必须调用 level.invalidateCapabilities(pos)。

// whenever a capability changes, appears, or disappears:
level.invalidateCapabilities(pos);

NeoForge 已处理区块加载/卸载、方块实体创建/移除等常见情况,其他情况需要显式处理。例如:

  • 先前返回的能力不再有效。
  • 提供能力但没有方块实体的方块被放置或改变状态,应在重写的 onPlace 中处理。
  • 提供能力但没有方块实体的方块被移除,应在重写的 onRemove 中处理。

普通方块的示例可查看 ComposterBlock.java。更多信息请参阅 IBlockCapabilityProvider 的 Javadoc。

注册能力

能力提供器最终负责提供能力。它是一个函数,能够返回能力实例;若无法提供,则返回 null。提供器针对:

  • 它所提供的具体能力;
  • 它所服务的方块实例、方块实体类型、实体类型或物品实例。

提供器必须在 RegisterCapabilitiesEvent 中注册。

使用 registerBlock 注册方块提供器,例如:

@SubscribeEvent  // on the mod event bus
public static void registerCapabilities(RegisterCapabilitiesEvent event) {
    event.registerBlock(
        Capabilities.ItemHandler.BLOCK, // capability to register for
        (level, pos, state, be, side) -> <return the IItemHandler>,
        // blocks to register for
        MY_ITEM_HANDLER_BLOCK,
        MY_OTHER_ITEM_HANDLER_BLOCK
    );
}

通常会针对特定方块实体类型注册,因此还提供 registerBlockEntity 辅助方法:

event.registerBlockEntity(
    Capabilities.ItemHandler.BLOCK, // capability to register for
    MY_BLOCK_ENTITY_TYPE, // block entity type to register for
    (myBlockEntity, side) -> myBlockEntity.myIItemHandlerForTheGivenSide
);

重要警告 方块或方块实体提供器先前返回的能力不再有效时,必须调用 level.invalidateCapabilities(pos) 使缓存失效。更多信息见上文失效机制。

实体使用类似的 registerEntity 注册:

event.registerEntity(
    Capabilities.ItemHandler.ENTITY, // capability to register for
    MY_ENTITY_TYPE, // entity type to register for
    (myEntity, context) -> myEntity.myIItemHandlerForTheGivenContext
);

物品注册也类似。注意,提供器会收到物品堆:

event.registerItem(
    Capabilities.ItemHandler.ITEM, // capability to register for
    (itemStack, context) -> <return the IItemHandler for the itemStack>,
    // items to register for
    MY_ITEM,
    MY_OTHER_ITEM
);

为所有对象注册能力

如果需要为所有方块、实体或物品注册提供器,应遍历对应注册表,逐个注册。

例如,NeoForge 使用此系统,为所有 BucketItem(不含子类)注册流体处理器能力:

// 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);
    }
}

提供器按注册顺序接受查询。如果想在 NeoForge 已为某个对象注册的提供器之前运行,就为自己的 RegisterCapabilitiesEvent 处理器设置更高优先级。例如:

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!

NeoForge 自身注册的提供器列表,可查看 CapabilityHooks。


来源: NeoForged/Documentation(1.21.1)。中文翻译依据提交 4a680cdd4d15。