功能标志
功能标志让开发者为一组功能设置启用条件:只有满足所需标志,这些功能才可使用。它可以控制已注册的元素、游戏机制、数据包条目,或 Mod 自定义的其他系统。
常见用途是将实验性功能或元素放在实验标志之后,让用户在功能定稿前轻松开启并体验。
建议 不一定要添加自己的标志。如果原版已有适合用途的标志,可以直接让方块、物品、实体等依赖该标志。
例如在
1.21.3中扩充苍白橡木方块系列时,通常只希望在WINTER_DROP标志开启后显示这些内容。
创建功能标志
要创建功能标志,需要建立 JSON 文件,并在 neoforge.mods.toml 的 [[mods]] 块中通过 featureFlags 条目引用。路径必须相对于 resources 目录:
# In neoforge.mods.toml:
[[mods]]
# The file is relative to the output directory of the resources, or the root path inside the jar when compiled
# The 'resources' directory represents the root output directory of the resources
featureFlags="META-INF/feature_flags.json"
文件中定义一组功能标志名称,游戏初始化时会加载并注册它们。
{
"flags": [
// Identifier of a Feature flag to be registered
"examplemod:experimental"
]
}
获取功能标志
已注册的功能标志可通过 FeatureFlagRegistry.getFlag(Identifier) 获取。可以在 Mod 初始化期间的任意时刻获取;建议将结果保存供后续使用,而不是每次需要标志时都查询注册表。
// Look up the 'examplemod:experimental' Feature flag
public static final FeatureFlag EXPERIMENTAL = FeatureFlags.REGISTRY.getFlag(Identifier.fromNamespaceAndPath("examplemod", "experimental"));
功能元素
FeatureElement 是可以设置所需标志集合的注册表值。只有世界已启用的标志满足对应要求,玩家才能使用这些值。
功能元素被禁用时,对玩家完全隐藏,相关交互也会跳过。注意,这些元素仍存在于注册表中,只是在功能上无法使用。
以下是直接实现 FeatureElement 系统的全部注册表:
- Item(物品)
- Block(方块)
- EntityType(实体类型)
- MenuType(菜单类型)
- Potion(药水)
- MobEffect(状态效果)
- GameRule(游戏规则)
为元素设置标志
要让 FeatureElement 依赖你的功能标志,只需将它和其他所需标志传入相应的注册方法:
Item:Item.Properties#requiredFeaturesBlock:BlockBehaviour.Properties#requiredFeaturesEntityType:EntityType.Builder#requiredFeaturesMenuType:MenuType#newPotion:Potion#requiredFeaturesMobEffect:MobEffect#requiredFeaturesGameRule:GameRule#new
// These elements will only become available once the 'EXPERIMENTAL' flag is enabled
// Item
DeferredRegister.Items ITEMS = DeferredRegister.createItems("examplemod");
DeferredItem<Item> EXPERIMENTAL_ITEM = ITEMS.registerSimpleItem("experimental", props -> props
.requiredFeatures(EXPERIMENTAL) // mark as requiring the 'EXPERIMENTAL' flag
);
// Block
DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("examplemod");
// Do note that BlockBehaviour.Properties#ofFullCopy and BlockBehaviour.Properties#ofLegacyCopy will copy over the required features.
// This means that in 1.21.3, using BlockBehaviour.Properties.ofFullCopy(Blocks.PALE_OAK_WOOD) would have your block require the 'WINTER_DROP' flag.
DeferredBlock<Block> EXPERIMENTAL_BLOCK = BLOCKS.registerSimpleBlock("experimental", BlockBehaviour.Properties.of()
.requiredFeatures(EXPERIMENTAL) // mark as requiring the 'EXPERIMENTAL' flag
);
// BlockItems are special in that the required features are inherited from their respective Blocks.
// The same is also true for spawn eggs and their respective EntityTypes.
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) // mark as requiring the 'EXPERIMENTAL' flag
.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<>(
// Using vanilla's MenuSupplier:
// This is used when your menu is not encoding complex data during `player.openMenu`. Example:
// (windowId, inventory) -> new ExperimentalMenu(windowId, inventory),
// Using NeoForge's IContainerFactory:
// This is used when you wish to read complex data encoded during `player.openMenu`.
// Casting is important here, as `MenuType` specifically expects a `MenuSupplier`.
(IContainerFactory<ExperimentalMenu>) (windowId, inventory, buffer) -> new ExperimentalMenu(windowId, inventory, buffer),
FeatureFlagSet.of(EXPERIMENTAL) // mark as requiring the 'EXPERIMENTAL' flag
));
// 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) // mark as requiring the 'EXPERIMENTAL' flag
);
// 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) // mark as requiring the 'EXPERIMENTAL' flag
);
// GameRule
DeferredRegister<GameRule> GAME_RULES = DefeferredRegister.create(Registries.GAME_RULE, "examplemod");
DeferredHolder<GameRule, GameRule> EXPERIMENTAL_GAME_RULE = GAME_RULES.register("experimental", registryName -> new GameRule(
GameRuleCategory.MISC, GameRuleType.BOOL, BoolArgumentType.bool(), GameRuleTypeVisitor::visitBoolean, Codec.BOOL, bool -> bool ? 1 : 0, false,
FeatureFlagSet.of(EXPERIMENTAL) // mark as requiring the 'EXPERIMENTAL' flag
));
检查启用状态
要判断功能是否应启用,首先需要获取已启用的功能集合。有多种获取方式,常用且推荐的是 LevelReader#enabledFeatures。
level.enabledFeatures(); // from a 'LevelReader' instance
entity.level().enabledFeatures(); // from a 'Entity' instance
// Client Side
minecraft.getConnection().enabledFeatures();
// Server Side
server.getWorldData().enabledFeatures();
判断某个 FeatureFlagSet 是否启用时,可以将已启用的功能集合传给 FeatureFlagSet#isSubsetOf;判断特定 FeatureElement 是否启用时,可以调用 FeatureElement#isEnabled。
提示
ItemStack提供了专门的isItemEnabled(FeatureFlagSet)方法。即使底层Item所需功能与已启用功能不匹配,这个方法也会将空物品堆视为启用。条件允许时,建议优先使用它,而不是Item#isEnabled。
requiredFeatures.isSubsetOf(enabledFeatures);
featureElement.isEnabled(enabledFeatures);
itemStack.isItemEnabled(enabledFeatures);
功能包
另见:资源包、数据包和 pack.mcmeta
功能包不仅能加载资源或数据,还能启用一组指定的功能标志。这些标志定义在包根目录的 pack.mcmeta JSON 文件中,格式如下:
提示 这个文件不同于 Mod 的
resources/目录中的文件。它定义的是一个全新的功能包,因此必须放在独立文件夹中。
{
"features": {
"enabled": [
// Identifier of a Feature flag to be enabled
// Must be a valid registered flag
"examplemod:experimental"
]
},
"pack": { /*...*/ }
}
用户可以从外部来源获取数据包形式的功能包,也可以下载内置功能包的 Mod。两者的安装方式还取决于所在的物理端。
内置功能包
内置包随 Mod 一起分发,并通过 AddPackFindersEvent 事件提供给游戏。
@SubscribeEvent // on the mod event bus
public static void addFeaturePacks(final AddPackFindersEvent event) {
event.addPackFinders(
// Path relative to your mods 'resources' pointing towards this pack
// Take note this also defines your packs id using the following format
// mod/<namespace>:<path>`, e.g. `mod/examplemod:data/examplemod/datapacks/experimental`
Identifier.fromNamespaceAndPath("examplemod", "data/examplemod/datapacks/experimental"),
// What kind of resources are contained within this pack
// 'CLIENT_RESOURCES' for packs with client assets (resource packs)
// 'SERVER_DATA' for packs with server data (data packs)
PackType.SERVER_DATA,
// Display name shown in the Experiments screen
Component.literal("ExampleMod: Experiments"),
// In order for this pack to load and enable feature flags, this MUST be 'FEATURE',
// any other PackSource type is invalid here
PackSource.FEATURE,
// If this is true, the pack is always active and cannot be disabled, should always be false for feature packs
false,
// Priority to load resources from this pack in
// 'TOP' this pack will be prioritized over other packs
// 'BOTTOM' other packs will be prioritized over this pack
Pack.Position.TOP
);
}
在单人游戏中启用
- 创建新世界。
- 打开实验性内容界面。
- 开启所需的包。
- 点击
Done(完成)确认。
在多人游戏中启用
- 打开服务器的
server.properties。 - 将功能包 ID 添加到
initial-enabled-packs,多个包之间用,分隔。包 ID 在注册包查找器时指定,参见上面的示例。
外部功能包
外部功能包以数据包形式提供给用户。
在单人游戏中安装
- 创建新世界。
- 打开数据包选择界面。
- 将数据包 ZIP 文件拖入游戏窗口。
- 将新出现的数据包移入
Selected(已选择)列表。 - 点击
Done(完成)确认。
游戏会提示新选择的实验性功能,以及可能出现的错误、问题和崩溃。点击 Proceed(继续)确认;也可以点击 Details(详细信息),查看全部已选包及其启用功能的完整列表。
提示 外部功能包不会显示在实验性内容界面,该界面只显示内置功能包。
启用外部功能包后,若要禁用,可返回数据包界面,将它从
Selected(已选择)移回Available(可用)列表。
在多人游戏中安装
功能包只能在首次创建世界时启用,启用后不能禁用。
- 创建目录
./world/datapacks。 - 将数据包 ZIP 文件上传到该目录。
- 打开服务器的
server.properties。 - 将数据包 ZIP 文件名(不含
.zip)添加到initial-enabled-packs,多个包以,分隔。- 例如,
examplemod-experimental.zip应写为initial-enabled-packs=vanilla,examplemod-experimental。
- 例如,
数据生成
另见:数据生成
功能包可以在常规的 Mod 数据生成过程中生成。最适合与内置包结合使用,也可以将生成结果压缩为 ZIP,作为外部包分发。选择其中一种方式即可,不要同时作为外部包分发并作为内置包随 Mod 提供。
@SubscribeEvent // on the mod event bus
public static void gatherData(final GatherDataEvent.Client event) {
DataGenerator generator = event.getGenerator();
// To generate a feature pack, you must first obtain a pack generator instance for the desired pack.
// generator.getBuiltinDatapack(<shouldGenerate>, <namespace>, <path>);
// This will generate the feature pack into the following path:
// ./data/<namespace>/datapacks/<path>
PackGenerator featurePack = generator.getBuiltinDatapack(true, "examplemod", "experimental");
// Register a provider to generate the `pack.mcmeta` file.
featurePack.addProvider(output -> PackMetadataGenerator.forFeaturePack(
output,
// Description displayed in the Experiments screen
Component.literal("Enabled experimental features for ExampleMod"),
// Set of Feature flags this pack should enable
FeatureFlagSet.of(EXPERIMENTAL)
));
// Register additional providers (recipes, loot tables) to `featurePack` to write any generated resources into this pack, rather than the root pack.
}
来源: NeoForged/Documentation(26.1)。中文翻译依据提交
4a680cdd4d15。