功能标志

功能标志

功能标志让开发者为一组功能设置启用条件:只有满足所需标志,这些功能才可使用。它可以控制已注册的元素、游戏机制、数据包条目,或 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#requiredFeatures
  • Block:BlockBehaviour.Properties#requiredFeatures
  • EntityType:EntityType.Builder#requiredFeatures
  • MenuType:MenuType#new
  • Potion:Potion#requiredFeatures
  • MobEffect:MobEffect#requiredFeatures
  • GameRule: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
    );
}

在单人游戏中启用

  1. 创建新世界。
  2. 打开实验性内容界面。
  3. 开启所需的包。
  4. 点击 Done(完成)确认。

在多人游戏中启用

  1. 打开服务器的 server.properties。
  2. 将功能包 ID 添加到 initial-enabled-packs,多个包之间用 , 分隔。包 ID 在注册包查找器时指定,参见上面的示例。

外部功能包

外部功能包以数据包形式提供给用户。

在单人游戏中安装

  1. 创建新世界。
  2. 打开数据包选择界面。
  3. 将数据包 ZIP 文件拖入游戏窗口。
  4. 将新出现的数据包移入 Selected(已选择)列表。
  5. 点击 Done(完成)确认。

游戏会提示新选择的实验性功能,以及可能出现的错误、问题和崩溃。点击 Proceed(继续)确认;也可以点击 Details(详细信息),查看全部已选包及其启用功能的完整列表。

提示 外部功能包不会显示在实验性内容界面,该界面只显示内置功能包。

启用外部功能包后,若要禁用,可返回数据包界面,将它从 Selected(已选择)移回 Available(可用)列表。

在多人游戏中安装

功能包只能在首次创建世界时启用,启用后不能禁用。

  1. 创建目录 ./world/datapacks。
  2. 将数据包 ZIP 文件上传到该目录。
  3. 打开服务器的 server.properties。
  4. 将数据包 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。