交互流程

交互流程

玩家右键交互的处理过程相当复杂,也容易混淆。本篇会梳理整个流程,并说明各处应返回什么结果及其原因。

右键时会发生什么?

在世界任意位置右键时,会根据视线目标和双手中的 ItemStack 执行一系列操作。流程会调用多个方法,返回下文介绍的结果类型。多数方法在返回明确成功或明确失败时,会中止后续流程。为方便叙述,下文把这种结果称为“确定结果”。

  • 以鼠标右键和主手触发 InputEvent.InteractionKeyMappingTriggered。若事件被取消,流程结束。
  • 检查若干条件,例如玩家不在旁观模式、主手物品堆所需的功能标志全部启用等。任一检查失败,流程结束。
  • 根据视线目标执行不同逻辑:
    • 若目标是可触及且未超出世界边界的实体:
      • 触发 PlayerInteractEvent.EntityInteractSpecific。若被取消,流程结束。
      • 在目标实体上调用 Entity#interactAt。若返回确定结果,流程结束。
        • 为自有实体添加行为时,重写此方法;为原版实体添加行为时,使用事件。
      • 如果实体打开了界面(例如村民交易或运输矿车界面),流程结束。
      • 触发 PlayerInteractEvent.EntityInteract。若被取消,流程结束。
      • 在目标实体上调用 Entity#interact。若返回确定结果,流程结束。
        • 为自有实体添加行为时,重写此方法;为原版实体添加行为时,使用事件。
        • 对 Mob,Entity#interact 的重写处理拴绳、使用主手刷怪蛋生成幼体等操作,再将生物专用逻辑交给 Mob#mobInteract。Entity#interact 的结果规则也适用于此处。
      • 若目标是 LivingEntity,则对主手物品堆调用 Item#interactLivingEntity。若返回确定结果,流程结束。
    • 若目标是可触及且未超出世界边界的方块:
      • 触发 PlayerInteractEvent.RightClickBlock。若被取消,流程结束。也可以在该事件中单独禁止使用方块或物品。
      • 调用 IItemExtension#onItemUseFirst。若返回确定结果,流程结束。
      • 若玩家未潜行,且事件没有禁止方块使用,则触发 UseItemOnBlockEvent。若被取消,使用取消结果;否则调用 Block#useItemOn。若返回确定结果,流程结束。
      • 若 ItemInteractionResult 为 PASS_TO_DEFAULT_BLOCK_INTERACTION,且当前为主手,则调用 Block#useWithoutItem。若返回确定结果,流程结束。
      • 若事件没有禁止物品使用,则调用 Item#useOn。若返回确定结果,流程结束。
  • 调用 Item#use。若返回确定结果,流程结束。
  • 上述流程再执行一次,这次改用副手。

结果类型

共有三种结果类型:InteractionResult、ItemInteractionResult 和 InteractionResultHolder<T>。多数位置使用 InteractionResult;只有 Item#use 使用 InteractionResultHolder<ItemStack>;只有 BlockBehaviour#useItemOn 和 CauldronInteraction#interact 使用 ItemInteractionResult。

InteractionResult 枚举包含五个值:SUCCESS、CONSUME、CONSUME_PARTIAL、PASS 和 FAIL。此外还有 InteractionResult#sidedSuccess。原文描述其在服务器返回 SUCCESS、客户端返回 CONSUME。

InteractionResultHolder<T> 包装 InteractionResult,并附加 T 类型的上下文。T 可以是任意类型,但几乎总是 ItemStack。它提供枚举值的包装方法(#success、#consume、#pass 和 #fail),也提供 #sidedSuccess;原文同样描述后者在服务器调用 #success,客户端调用 #consume。

ItemInteractionResult 类似于 InteractionResult,专门处理对方块使用物品的情况。它有六个枚举值:SUCCESS、CONSUME、CONSUME_PARTIAL、PASS_TO_DEFAULT_BLOCK_INTERACTION、SKIP_DEFAULT_BLOCK_INTERACTION 和 FAIL。通过 #result 可映射为 InteractionResult,其中两个带 DEFAULT_BLOCK_INTERACTION 的值均表示 InteractionResult#PASS。它也提供 #sidedSucess。

通常,各值的含义如下:

  • InteractionResult#sidedSuccess(需要时使用 InteractionResultHolder#sidedSuccess 或 ItemInteractionResult#sidedSucess):操作视为成功,并希望手臂挥动。流程结束。
  • InteractionResult#SUCCESS(或 InteractionResultHolder#success、ItemInteractionResult#SUCCESS):操作视为成功,并希望手臂只在一端挥动。只有出于某种原因,需要在另一逻辑端返回不同结果时才使用。流程结束。
  • InteractionResult#CONSUME(或 InteractionResultHolder#consume、ItemInteractionResult#CONSUME):操作视为成功,但不希望手臂挥动。流程结束。
  • InteractionResult#CONSUME_PARTIAL:与 CONSUME 基本相同,区别主要在 Item#useOn 中。
    • ItemInteractionResult#CONSUME_PARTIAL 在 BlockBehaviour#useItemOn 中也有类似用途。
  • InteractionResult.FAIL(或 InteractionResultHolder#fail、ItemInteractionResult#FAIL):物品功能视为失败,不应继续其他交互。流程结束。任何位置都可使用,但在 Item#useOn 和 Item#use 之外应谨慎,许多时候 InteractionResult.PASS 更合适。
  • InteractionResult.PASS(需要时使用 InteractionResultHolder#pass):操作既不算成功,也不算失败,流程继续。除另有说明外,这是默认行为。
    • ItemInteractionResult#PASS_TO_DEFAULT_BLOCK_INTERACTION 允许主手调用 BlockBehaviour#useWithoutItem;#SKIP_DEFAULT_BLOCK_INTERACTION 则完全跳过该方法。除另有说明外,默认使用前者。

有些方法具有特殊行为或要求,下文会分别说明。

IItemExtension#onItemUseFirst

InteractionResult#sidedSuccess 和 InteractionResult.CONSUME 在这里不起作用。只应使用 InteractionResult.SUCCESS、InteractionResult.FAIL 或 InteractionResult.PASS。

Item#useOn

如果操作应视为成功,但不希望挥动手臂,也不希望增加 ITEM_USED 统计值,请使用 InteractionResult.CONSUME_PARTIAL。

Item#use

这是唯一返回 InteractionResultHolder<ItemStack> 的位置。如果结果中的物品堆发生了变化,它将替换发起使用操作的物品堆。

默认实现会在物品可食用且玩家能吃时(玩家饥饿,或物品始终可食用)返回 InteractionResultHolder#consume;物品可食用但玩家不能吃时返回 #fail;物品不可食用时返回 #pass。

处理主手时,如果在此返回 InteractionResultHolder#fail,将阻止副手行为。如果希望副手行为继续执行(通常如此),应返回 InteractionResultHolder#pass。


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