功能标志(Feature Flags)
功能标志(Feature flags)是一套允许开发者通过一组标志来控制某些功能开放与否的系统。这些受控内容可以是已注册的元素、游戏机制、数据包条目,或者你的模组中特有的其他系统。
一个常见的用例是将实验性功能或元素放在一个实验标志(experimental flag)后面,这样用户就可以方便地开启这些内容并进行尝试,而无需等到功能最终定稿。
你并不是必须自己添加新的标志。如果你发现原版已有的标志正好适合你的需求,可以直接用该标志为你的方块、物品、生物等进行标记。
比如在 1.21.3 版本中,如果你想扩展 Pale Oak 木材方块系列,通常只希望这些方块在 WINTER_DROP 标志被启用时才出现。
创建功能标志(Feature Flag)
要创建新的功能标志,需要新建一个 JSON 文件,并在你的 neoforge.mods.toml 文件的 [[mods]] 块中,通过 featureFlags 条目进行引用。指定的路径必须是相对于 resources 目录的:
# 在 neoforge.mods.toml 文件中:
[[mods]]
# 文件路径相对于资源输出目录,或编译后 jar 包内的根路径
# 'resources' 目录即资源文件的根输出目录
featureFlags="META-INF/feature_flags.json"
该条目的定义包括一个功能标志名称的列表,这些标志会在游戏初始化时加载并注册。
{
"flags": [
// 要注册的功能标志的标识符
"examplemod:experimental"
]
}
获取功能标志(Feature Flag)
已注册的功能标志可以通过 FeatureFlagRegistry.getFlag(ResourceLocation) 获取。你可以在模组初始化的任意阶段进行获取,建议获取后存储在某处以便后续使用,而不是每次需要标志时都去查询注册表(Registry)。
// 查找 'examplemod:experimental' 功能标志
public static final FeatureFlag EXPERIMENTAL = FeatureFlags.REGISTRY.getFlag(ResourceLocation.fromNamespaceAndPath("examplemod", "experimental"));
功能元素(Feature Elements)
FeatureElement 是指可以被赋予一组所需标志(required flags)的注册值(registry value)。只有当关卡中启用的标志与这些元素所需的标志相匹配时,这些元素才会对玩家可用。
当某个功能元素被禁用时,它会完全从玩家视野中隐藏,所有相关交互都会被跳过。需要注意的是,这些被禁用的元素依然存在于注册表(Registry)中,只是无法被实际使用。
下列注册表是直接实现了 FeatureElement 系统的完整列表:
- Item(物品)
- Block(方块)
- EntityType(实体类型)
- MenuType(菜单类型)
- Potion(药水)
- MobEffect(生物效果)
标记元素(Flagging Elements)
要将某个 FeatureElement 标记为需要你的功能标志,只需在对应的注册方法中,将该标志及其他所需标志一同传入即可:
Item:Item.Properties#requiredFeaturesBlock:BlockBehaviour.Properties#requiredFeaturesEntityType:EntityType.Builder#requiredFeaturesMenuType:MenuType#newPotion:Potion#requiredFeaturesMobEffect:MobEffect#requiredFeatures
// 这些元素只有在启用 'EXPERIMENTAL'(实验性)标志后才会可用
// 物品(Item)
DeferredRegister.Items ITEMS = DeferredRegister.createItems("examplemod");
DeferredItem<Item> EXPERIMENTAL_ITEM = ITEMS.registerSimpleItem("experimental", new Item.Properties()
.requiredFeatures(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
);
// 方块(Block)
DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("examplemod");
// 请注意,BlockBehaviour.Properties#ofFullCopy 和 BlockBehaviour.Properties#ofLegacyCopy 会复制所需的特性标志。
// 这意味着在 1.21.3 版本中,使用 BlockBehaviour.Properties.ofFullCopy(Blocks.PALE_OAK_WOOD) 会让你的方块需要 'WINTER_DROP'(冬季掉落)标志。
DeferredBlock<Block> EXPERIMENTAL_BLOCK = BLOCKS.registerSimpleBlock("experimental", BlockBehaviour.Properties.of()
.requiredFeatures(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
);
// BlockItem 有一个特殊之处,它所需的特性标志会从其对应的 Block 继承。
// 生成蛋(spawn eggs)和它们对应的 EntityType 也是如此。
DeferredItem<BlockItem> EXPERIMENTAL_BLOCK_ITEM = ITEMS.registerSimpleBlockItem(EXPERIMENTAL_BLOCK);
// 实体类型(EntityType)
DeferredRegister<EntityType<?>> ENTITY_TYPES = DeferredRegister.create(Registries.ENTITY_TYPE, "examplemod");
DeferredHolder<EntityType<?>, EntityType<ExperimentalEntity>> EXPERIMENTAL_ENTITY = ENTITY_TYPES.register("experimental", registryName -> EntityType.Builder.of(ExperimentalEntity::new, MobCategory.AMBIENT)
.requiredFeatures(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
.build(ResourceKey.create(Registries.ENTITY_TYPE, registryName))
);
// 菜单类型(MenuType)
DeferredRegister<MenuType<?>> MENU_TYPES = DeferredRegister.create(Registries.MENU, "examplemod");
DeferredHolder<MenuType<?>, MenuType<ExperimentalMenu>> EXPERIMENTAL_MENU = MENU_TYPES.register("experimental", () -> new MenuType<>(
// 使用原版的 MenuSupplier:
// 当你的菜单在 `player.openMenu` 期间没有编码复杂数据时使用。例如:
// (windowId, inventory) -> new ExperimentalMenu(windowId, inventory),
// 使用 NeoForge 的 IContainerFactory:
// 当你希望在 `player.openMenu` 期间读取编码的复杂数据时使用。
// 这里的类型转换很重要,因为 `MenuType` 明确要求一个 `MenuSupplier`。
(IContainerFactory<ExperimentalMenu>) (windowId, inventory, buffer) -> new ExperimentalMenu(windowId, inventory, buffer),
FeatureFlagSet.of(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
));
// 生物效果(MobEffect)
DeferredRegister<MobEffect> MOB_EFFECTS = DeferredRegister.create(Registries.MOB_EFFECT, "examplemod");
DeferredHolder<MobEffect, ExperimentalMobEffect> EXPERIMENTAL_MOB_EEFECT = MOB_EFFECTS.register("experimental", registryName -> new ExperimentalMobEffect(MobEffectCategory.NEUTRAL, CommonColors.WHITE)
.requiredFeatures(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
);
// 药水(Potion)
DeferredRegister<Potion> POTIONS = DeferredRegister.create(Registries.POTION, "examplemod");
DeferredHolder<Potion, ExperimentalPotion> EXPERIMENTAL_POTION = POTIONS.register("experimental", registryName -> new ExperimentalPotion(registryName.toString(), new MobEffectInstance(EXPERIMENTAL_MOB_EEFECT))
.requiredFeatures(EXPERIMENTAL) // 标记为需要 'EXPERIMENTAL'(实验性)标志
);
验证启用状态(Validating Enabled Status)
要判断某些功能(features)是否应该被启用,首先需要获取已启用功能的集合。获取方式有多种,但最常用且推荐的方法是通过 LevelReader#enabledFeatures。
level.enabledFeatures(); // 从一个 'LevelReader' 实例获取
entity.level().enabledFeatures(); // 从 一个 'Entity' 实例获取
// 客户端
minecraft.getConnection().enabledFeatures();
// 服务端
server.getWorldData().enabledFeatures();
如果需要判断某个 FeatureFlagSet 是否被启用,可以将已启用功能集传递给 FeatureFlagSet#isSubsetOf。若要判断具体的某个 FeatureElement 是否启用,可以调用 FeatureElement#isEnabled。
ItemStack 提供了 一个特殊的 isItemEnabled(FeatureFlagSet) 方法。这样即使空的物品堆(stack)所需的功能与已启用功能不匹配,也会被视为已启用。建议尽可能优先使用此方法,而不是 Item#isEnabled。
requiredFeatures.isSubsetOf(enabledFeatures); // 判断所需功能集合是否为已启用集合的子集
featureElement.isEnabled(enabledFeatures); // 判断某个功能元素是否被启用
itemStack.isItemEnabled(enabledFeatures); // 判断物品堆是否被启用
功能包(Feature Packs)
另见:资源包、数据包 和 Pack.mcmeta
功能包(Feature Packs)是一种特殊的数据包,不仅可以加载资源和/或数据,还可以切换一组功能标志(feature flags)的启用状态。这些标志在功能包根目录下的 pack.mcmeta JSON 文件中定义,格式如下:
此文件与模组 resources/ 目录下的文件不同。它定义的是一个全新的功能包,因此必须放在独立的文件夹中。
{
"features": {
"enabled": [
// 要启用的功能标志的标识符
// 必须是已注册的有效标志
"examplemod:experimental"
]
},
"pack": { /*...*/ }
}
用户获取功能包的方式主要有两种:一是作为数据包(datapack)从外部来源安装,二是下载带有内置功能包的模组。两种方式的安装方法会根据 物理端 的不同而有所区别。
内置(Built-In)
内置包(Built-in packs)会随你的模组一起打包,并通过 AddPackFindersEvent 事件向游戏注册。
@SubscribeEvent
public static void addFeaturePacks(final AddPackFindersEvent event) {
event.addPackFinders(
// 路径是相对于你的模组 'resources' 目录,指向此数据包
// 注意,这也定义了你的数据包 id,格式如下
// mod/<namespace>:<path>,例如:mod/examplemod:data/examplemod/datapacks/experimental
ResourceLocation.fromNamespaceAndPath("examplemod", "data/examplemod/datapacks/experimental"),
// 此数据包包含的资源类型
// 'CLIENT_RESOURCES' 用于包含客户端资源(资源包)
// 'SERVER_DATA' 用于包含服务端数据(数据包)
PackType.SERVER_DATA,
// 在实验界面中显示的名称
Component.literal("ExampleMod: Experiments"),
// 为了让此数据包加载并启用功能标志,必须设置为 'FEATURE',
// 其他任何 PackSource 类型在这里都是无效的
PackSource.FEATURE,
// 如果为 true,则该数据包始终激活且无法禁用,功能包应始终为 false
false,
// 加载此数据包资源的优先级
// 'TOP' 表示此数据包优先于其他数据包加载
// 'BOTTOM' 表示其他数据包优先于此数据包加载
Pack.Position.TOP
);
}
单人模式下的启用方法(Enabling in Singleplayer)
- 创建一个新世界。
- 进入实验(Experiments)界面。
- 打开你想启用的数据包。
- 点击
完成(Done)以确认更改。
多人模式下的启用方法(Enabling in Multiplayer)
- 打开你的服务器的
server.properties文件。 - 将功能包 id 添加到
initial-enabled-packs,多个包用,分隔。数据包 id 在注册 pack finder 时定义,如上所示。
外部功能包(External)
外部功能包以数据包(datapack)的形式提供给你的用户。
单人模式下的安装方法(Installation in Singleplayer)
- 创建一个新世界。
- 进入数据包选择界面。
- 将数据包 zip 文件拖放到游戏窗口中。
- 将新出现的数据包移动到
已选中(Selected)数据包列表中。 - 点击
完成(Done)以确认更改。
游戏现在会警告你新选择的实验性功能、潜在的 bug、问题及崩溃风险。你可以点击 继续(Proceed)确认更改,或点击 详情(Details)查看所有已选数据包及其将启用的功能的详细列表。
外部功能包不会出现在实验(Experiments)界面中。实验界面只会显示内置功能包。
要在启用后禁用外部功能包,请重新进入数据包界面,并将外部数据包从 已选中(Selected)移动回 可用(Available)。
多人模式下的安装方法(Installation in Multiplayer)
功能包只能在世界初次创建时启用,启用后无法禁用。
- 创建目录
./world/datapacks - 将数据包(datapack)压缩包上传到新建的目录中
- 打开你的服务器的
server.properties文件