进度

进度

进度是玩家可以完成的任务,可能推动游戏进程。玩家直接参与的任何操作都可以触发进度。

进度 requirements 逻辑图:条件 a 或 b 使第一组通过,条件 c 或 d 使第二组通过,两组必须同时通过才完成进度;示例 a 与 c 通过可完成,仅 a 与 b 通过仍不能完成。图脚注明依据 Minecraft Wiki 修正 Forge 原文的 AND/OR 关系,并列出两方来源。

原创说明图;进度条件的逻辑依据 Minecraft Wiki 修正 Forge 原文的表述。

原版进度均通过 JSON 数据驱动,因此创建新进度不需要 Mod,只需要数据包。如何创建进度并放入 Mod 的 resources,参见 Minecraft Wiki。此外,进度可根据现有信息(如 Mod 是否加载、物品是否存在)条件加载并使用默认值。

进度条件

解锁进度需要满足指定条件。触发器跟踪条件,在执行特定操作时运行,例如击杀实体、改变物品栏或繁殖动物。进度加载到游戏时,定义的条件会被读取并作为监听器添加到触发器。随后调用触发函数(通常名为 #trigger),检查所有监听器,判断当前状态是否符合进度条件。只有完成所有要求获得进度后,监听器才会移除。

要求定义为字符串数组的数组,字符串表示进度中指定的条件名称。每个子数组中至少一个条件必须满足,并且所有子数组都必须满足。以下示例需要满足条件 1 或 2,且同时满足条件 3 或 4:

带注释和省略号的示例用于说明结构;实际 JSON 文件需移除注释并补全内容。

// 在某个进度 JSON 中
// 定义要满足的条件
"criteria": {
  "example_criterion1": { /*...*/ },
  "example_criterion2": { /*...*/ },
  "example_criterion3": { /*...*/ },
  "example_criterion4": { /*...*/ }
},
// 条件 1 或 2 成立,且条件 3 或 4 成立
"requirements": [
  ["example_criterion1", "example_criterion2"],
  ["example_criterion3", "example_criterion4"]
]

译注:Forge 原文将此例的 AND / OR 关系写反。此处按 Minecraft Wiki 的进度 JSON 格式说明更正。

原版触发器列表位于 CriteriaTriggers,JSON 格式参见 Minecraft Wiki。

自定义条件触发器

为自定义 AbstractCriterionTriggerInstance 子类实现 SimpleCriterionTrigger,即可创建自定义触发器。

AbstractCriterionTriggerInstance 子类

AbstractCriterionTriggerInstance 表示 criteria 对象中定义的单个条件。触发器实例负责保存定义的条件、返回输入是否匹配,并将实例写入 JSON 供数据生成使用。

条件通常通过构造函数传入。父类构造函数要求提供触发器注册表名称,以及玩家必须满足的 ContextAwarePredicate 条件。触发器名称应直接提供给父类,玩家条件应作为构造函数参数。

// ID 是触发器的注册表名称
public ExampleTriggerInstance(ContextAwarePredicate player, ItemPredicate item) {
  super(ID, player);
  // 保存必须满足的物品条件
}

触发器实例通常有静态工厂方法,方便数据生成时创建,也可以静态导入这些方法而不是类本身。

public static ExampleTriggerInstance instance(ContextAwarePredicate player, ItemPredicate item) {
  return new ExampleTriggerInstance(player, item);
}

还应重写 #serializeToJson,将实例条件添加到其他 JSON 数据:

@Override
public JsonObject serializeToJson(SerializationContext context) {
  JsonObject obj = super.serializeToJson(context);
  // 将条件写入 JSON
  return obj;
}

最后添加接收当前数据状态并返回用户是否满足必要条件的方法。玩家条件已通过 SimpleCriterionTrigger#trigger(ServerPlayer, Predicate) 检查。多数触发器实例将此方法命名为 #matches。

// 每种实例的方法各不相同,因此不是重写方法
public boolean matches(ItemStack stack) {
  // ItemPredicate 匹配物品堆,因此输入是堆
  return this.item.matches(stack);
}

SimpleCriterionTrigger

SimpleCriterionTrigger<T> 子类(T 为触发器实例类型)负责指定触发器注册表名称、创建实例,以及提供检查实例并在成功时运行监听器的方法。

#getId 提供的注册表名称应与触发器实例中的名称一致。通过 #createInstance 从 JSON 读取条件并创建实例:

@Override
public ExampleTriggerInstance createInstance(JsonObject json, ContextAwarePredicate player, DeserializationContext context) {
  // 从 JSON 读取条件:item
  return new ExampleTriggerInstance(player, item);
}

最后定义方法,检查所有实例并在条件满足时运行监听器。方法接收 ServerPlayer 及 AbstractCriterionTriggerInstance 子类的匹配方法定义的其他数据。内部应调用 SimpleCriterionTrigger#trigger 以正确检查所有监听器。多数触发器将其命名为 #trigger。

// 每种触发器的方法各不相同,因此不是重写方法
public void trigger(ServerPlayer player, ItemStack stack) {
  this.trigger(player,
    // AbstractCriterionTriggerInstance 子类中的条件检查方法
    triggerInstance -> triggerInstance.matches(stack)
  );
}

随后在 FMLCommonSetupEvent 期间使用 CriteriaTriggers#register 注册实例。

重要:CriteriaTriggers#register 不是线程安全的,必须通过 FMLCommonSetupEvent#enqueueWork 加入同步工作队列。

调用触发器

每当执行所检查的操作时,应调用 SimpleCriterionTrigger 子类定义的 #trigger:

// 在执行操作的代码中;EXAMPLE_CRITERIA_TRIGGER 是自定义条件触发器
public void performExampleAction(ServerPlayer player, ItemStack stack) {
  // 执行操作的代码
  EXAMPLE_CRITERIA_TRIGGER.trigger(player, stack);
}

进度奖励

完成进度时可发放奖励,包括经验值、战利品表、配方书配方,或以创造模式玩家身份执行的函数,也可以组合使用:

// 在某个进度 JSON 中
"rewards": {
  "experience": 10,
  "loot": [
    "minecraft:example_loot_table",
    "minecraft:example_loot_table2" // ...
  ],
  "recipes": [
    "minecraft:example_recipe",
    "minecraft:example_recipe2" // ...
  ],
  "function": "minecraft:example_function"
}

原文:Advancements。