MC Forge1.20.1 mod开发学习笔记(数据生成、食物)

数据生成

为了简化我们自己手动编写json配置文件的过程,mdk提供了一些类来帮助我们进行json生成。

生成器

这里是生成器的汇总部分,我们通过事件总线,把各类数据的生成器汇总到一起来执行(使用DataGenerator来添加各类数据的provider),目前主要包含配方、战利品、方块/物品标签、方块、物品、语言文件。

其中配方、战利品、方块/物品标签是包含在服务端,方块、物品、语言文件放在客户端(方便客户端更换材质包与翻译)

java 复制代码
@Mod.EventBusSubscriber(modid = TestMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class ModDataGenerator {
    @SubscribeEvent
    public static void gatherData(GatherDataEvent event) {
        DataGenerator generator = event.getGenerator();
        PackOutput packOutput = generator.getPackOutput();
        ExistingFileHelper existingFileHelper = event.getExistingFileHelper();
        CompletableFuture<HolderLookup.Provider> lookupProvider = event.getLookupProvider();

        generator.addProvider(event.includeServer(), new ModRecipesProvider(packOutput));
        generator.addProvider(event.includeServer(), ModLootTableProvider.create(packOutput));

        BlockTagsProvider blockTagsProvider =
                generator.addProvider(event.includeServer(), new ModBlockTagsProvider(packOutput, lookupProvider, existingFileHelper));
        generator.addProvider(event.includeServer(), new ModItemTagsProvider(packOutput, lookupProvider, blockTagsProvider.contentsGetter(), existingFileHelper));

        generator.addProvider(event.includeClient(), new ModBlockStateProvider(packOutput, existingFileHelper));
        generator.addProvider(event.includeClient(), new ModItemModelsProvider(packOutput, existingFileHelper));
        generator.addProvider(event.includeClient(), new ModEnUsLangProvider(packOutput));
        generator.addProvider(event.includeClient(), new ModZhCnLangProvider(packOutput));
    }
}

方块model、blockstate

对于方块类物品的models和blockstates,有BlockStateProvider可以使用

我们看一下它的源码:

首先是构造函数,output是生成json数据保存的路径,modid是命名空间,exFileHelper是资源文件检查工具(避免引用纹理缺失的数据),里面已经封装了BlockModelProvider和ItemModelProvider,能直接生成方块和方块物品的model以及方块的blockstate

接下来可以看到一个run函数,和一个抽象方法registerStatesAndModels(这个就是需要我们重写自定义的方法),run函数先清空了之前生成的json,然后运行我们要生成的内容,接着把这些futures任务注册到异步事件列表中,后续我们从外部的DataGenerator依次调用这些事件运行来生成json。

接下来的一大部分都是各种各样的方块生成方法,可供我们调用:

这里我们只用simpleBlockWithItem,生成最简单的model与blockstate

先包装一下我们模组的BlockStateProvider

java 复制代码
public class ModBlockStateProvider extends BlockStateProvider {
    public ModBlockStateProvider(PackOutput output, ExistingFileHelper exFileHelper) {
        super(output, TestMod.MOD_ID, exFileHelper);
    }

    @Override
    protected void registerStatesAndModels() {
        simpleBlockWithItem(ModBlocks.CANDY_BLOCK.get(),cubeAll(ModBlocks.CANDY_BLOCK.get()));
        simpleBlockWithItem(ModBlocks.CANDY_ORE.get(),cubeAll(ModBlocks.CANDY_ORE.get()));
    }

}

我们继承了BlockStateProvider,后续调用的时候可以直接使用DataGenerator添加我们自定义的Provider,从而执行数据生成事件。

物品model

比方块更加简单,ItemModelProvider中也只有basicItem一个函数,需要注意的是,只添加物品,不用再添加方块物品了。

java 复制代码
public class ModItemModelsProvider extends ItemModelProvider {

    public ModItemModelsProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
        super(output, TestMod.MOD_ID, existingFileHelper);
    }

    @Override
    protected void registerModels() {
        basicItem(ModItems.CANDY_STICK.get());
        basicItem(ModItems.CANDY_STAFF.get());
    }
}

翻译文件lang

没啥好讲的,继承一下LanguageProvider,复写addTranslations,然后一个一个加翻译就行了

注意物品和方块由于使用的延迟注册器,最好用get获取他们的名字

java 复制代码
public class ModEnUsLangProvider extends LanguageProvider {
    public ModEnUsLangProvider(PackOutput output) {
        super(output, TestMod.MOD_ID, "en_us");
    }

    @Override
    protected void addTranslations() {
        add(ModItems.CANDY_STAFF.get(), "Candy Staff");
        add(ModItems.CANDY_STICK.get(), "Candy Stick");

        add(ModBlocks.CANDY_BLOCK.get(), "Candy Block");
        add(ModBlocks.CANDY_ORE.get(), "Candy Ore");

        add("itemGroup.test_tab", "Test Tab");
    }
}

配方recipe

这里注意,要继承RecipeProvider,同时实现IConditionBuilder的接口,但是IConditionBuilder具有默认方法,所以我们也不需要复写。

我们只需要实现buildRecipes函数,其中采用的也是建造者模式。这些方法都比较显然,但是注意这些配方的命名空间是根据合成产物的命名空间放置的,所以要注意把部分配方的保存位置在save中进行修改。

java 复制代码
public class ModRecipesProvider extends RecipeProvider implements IConditionBuilder {
    public ModRecipesProvider(PackOutput pOutput) {
        super(pOutput);
    }

    @Override
    protected void buildRecipes(Consumer<FinishedRecipe> pWriter) {
        ShapedRecipeBuilder.shaped(RecipeCategory.BUILDING_BLOCKS, ModBlocks.CANDY_BLOCK.get())
                .group("candy_block")
                .define('#', Items.SUGAR)
                .pattern("###")
                .pattern("###")
                .pattern("###")
                .unlockedBy(getHasName(Items.SUGAR), has(Items.SUGAR))
                .save(pWriter);
        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ModItems.CANDY_STICK.get())
                .group("candy_stick")
                .define('#', Items.SUGAR)
                .pattern("#")
                .pattern("#")
                .unlockedBy(getHasName(Items.SUGAR), has(Items.SUGAR))
                .save(pWriter);
        ShapedRecipeBuilder.shaped(RecipeCategory.COMBAT, ModItems.CANDY_STAFF.get())
                .group("candy_staff")
                .define('c', ModItems.CANDY_STICK.get())
                .define('d', Items.DIAMOND)
                .pattern("  d")
                .pattern(" c ")
                .pattern("c  ")
                .unlockedBy(getHasName(ModItems.CANDY_STICK.get()), has(ModItems.CANDY_STICK.get()))
                .unlockedBy(getHasName(Items.DIAMOND), has(Items.DIAMOND))
                .save(pWriter);
        ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, Items.SUGAR, 9)
                .group("sugar")
                .requires(ModBlocks.CANDY_BLOCK.get())
                .unlockedBy(getHasName(Items.SUGAR), has(Items.SUGAR))
                .save(pWriter, TestMod.MOD_ID + ":" + "sugar_from_candy_block");
    }
}

战利品loot_table

首先,我们已经知道战利品列表有很多种,比如实体掉落、战利品箱、方块掉落、钓鱼等等

这里我们只是用了方块掉落(只用一个次级的Provider),所以我们继承BlockLootSubProvider,实现generate方法。

写之前先阅读一下BlockLootSubProvider的源码

以青金石掉落为例,json文件:

java 复制代码
{
  "type": "minecraft:block",
  "pools": [
    {
      "bonus_rolls": 0.0,
      "entries": [
        {
          "type": "minecraft:alternatives",
          "children": [
            {
              "type": "minecraft:item",
              "conditions": [
                {
                  "condition": "minecraft:match_tool",
                  "predicate": {
                    "enchantments": [
                      {
                        "enchantment": "minecraft:silk_touch",
                        "levels": {
                          "min": 1
                        }
                      }
                    ]
                  }
                }
              ],
              "name": "minecraft:lapis_ore"
            },
            {
              "type": "minecraft:item",
              "functions": [
                {
                  "add": false,
                  "count": {
                    "type": "minecraft:uniform",
                    "max": 9.0,
                    "min": 4.0
                  },
                  "function": "minecraft:set_count"
                },
                {
                  "enchantment": "minecraft:fortune",
                  "formula": "minecraft:ore_drops",
                  "function": "minecraft:apply_bonus"
                },
                {
                  "function": "minecraft:explosion_decay"
                }
              ],
              "name": "minecraft:lapis_lazuli"
            }
          ]
        }
      ],
      "rolls": 1.0
    }
  ],
  "random_sequence": "minecraft:blocks/lapis_ore"
}

对应的生成代码:

java 复制代码
protected LootTable.Builder createLapisOreDrops(Block pBlock) {
        return createSilkTouchDispatchTable(pBlock, (LootPoolEntryContainer.Builder)this
.applyExplosionDecay(pBlock, LootItem.lootTableItem(Items.LAPIS_LAZULI)
.apply(SetItemCountFunction.setCount(UniformGenerator.between(4.0F, 9.0F)))
.apply(ApplyBonusCount.addOreBonusCount(Enchantments.BLOCK_FORTUNE))));
    }

这里可以知道,createSilkTouchDispatchTable,就是创建了alternatives的函数,然后把精准采集的掉落物放到了第一个。

然后接下来的几个apply都相当于加function字段,然后并且设置对应的函数。

接下来我们可以仿照上面的写法,自定义一个创建掉落物的函数,其实这里也不用传入item作为参数,因为每个方块掉落物一般不会完全一样,设置的属性也一般都不一样,最好还是每个物品单独写一个createXXXDrops。

对于普通方块,直接使用dropSelf生成即可。

java 复制代码
public class ModBlockLootTablesProvider extends BlockLootSubProvider {
    public ModBlockLootTablesProvider() {
        super(Set.of(), FeatureFlags.REGISTRY.allFlags());
    }

    @Override
    protected void generate() {
        dropSelf(ModBlocks.CANDY_BLOCK.get());
        add(ModBlocks.CANDY_ORE.get(), block -> createLapisOreLikeDrops(ModBlocks.CANDY_ORE.get(), Items.SUGAR));
    }
    protected LootTable.Builder createLapisOreLikeDrops(Block pBlock, Item item) {
        return createSilkTouchDispatchTable(pBlock,
                this.applyExplosionDecay(pBlock, LootItem.lootTableItem(item)
                        .apply(SetItemCountFunction.setCount(UniformGenerator.between(4.0F, 9.0F)))
                        .apply(ApplyBonusCount.addOreBonusCount(Enchantments.BLOCK_FORTUNE))));
    }
    @Override
    protected Iterable<Block> getKnownBlocks() {
        return ModBlocks.BLOCKS.getEntries().stream().map(RegistryObject::get)::iterator;
    }

}

完成SubProvider的复写之后

我们再包装一下,让他生成LootTableProvider

先看一下LootTableProvider的内容,Set里面定位ResourceLocation,List就是新创建的SubProviders,对于SubProviderEntry,设置类型BLOCK

可设置的战利品类型:

包装结果:

java 复制代码
public class ModLootTableProvider {
    public static LootTableProvider create(PackOutput packOutput){
        return new LootTableProvider(packOutput, Set.of(), List.of(
                new LootTableProvider.SubProviderEntry(ModBlockLootTablesProvider::new, LootContextParamSets.BLOCK)
        ));
    }
}

标签tag

这个相对来说就没那么复杂了

tag设定标签,add往这个标签下添加物品。

稍微包装一下就搞定了

java 复制代码
public class ModBlockTagsProvider extends BlockTagsProvider {
    public ModBlockTagsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider, @Nullable ExistingFileHelper exFileHelper) {
        super(output, lookupProvider, TestMod.MOD_ID, exFileHelper);
    }

    @Override
    protected void addTags(HolderLookup.Provider pProvider) {
        tag(BlockTags.MINEABLE_WITH_PICKAXE)
                .add(ModBlocks.CANDY_ORE.get());
        tag(BlockTags.NEEDS_STONE_TOOL)
                .add(ModBlocks.CANDY_ORE.get());
    }
}

目前Item还没添加标签:

java 复制代码
public class ModItemTagsProvider extends ItemTagsProvider {
    public ModItemTagsProvider(PackOutput pOutput, CompletableFuture<HolderLookup.Provider> pLookupProvider, CompletableFuture<TagLookup<Block>> pBlockTags, @Nullable ExistingFileHelper existingFileHelper) {
        super(pOutput, pLookupProvider, pBlockTags, TestMod.MOD_ID, existingFileHelper);
    }

    @Override
    protected void addTags(HolderLookup.Provider provider) {

    }
}

运行数据生成

点击右边的大象,双击runData,可以看到数据生成的结果,以及花费的时间。

食物

首先我们要理解MC食物机制的原理,先读一下源码:

游戏中一点饥饿度就是半个鸡腿,饱和度在原版中是看不见的,它最大值为当前饥饿度,Foods类中的.nutrition表示食物营养值(能够恢复的饥饿度),saturationMod表示食物的饱和度系数。

吃东西的数据更新方式如下:

新饥饿度=max(原饥饿度+食物营养值,20)

新饱和度=max(原饱和度+食物营养值*饱和度系数*2,新饥饿度)

更多的机制内容见百科

饥饿 - 中文Minecraft Wiki镜像_BWIKI_哔哩哔哩

我们在Item文件夹下添加一个ModFoods.java,然后在其中编写食物的效果:

在MobEffectInstance中,第一个表示效果类型,第二个是效果持续时间,第三个是效果等级,外面的是获取该效果的概率。

.alwaysEat可以让玩家在饱腹的情况下继续吃该食物。

java 复制代码
public class ModFoods {
        public static final FoodProperties CANDY_STICK = new FoodProperties.Builder().nutrition(1).saturationMod(0.5f)
                .effect(() -> new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 200, 1), 0.4f)
                .effect(() -> new MobEffectInstance(MobEffects.WEAKNESS, 400, 1), 0.1f)
                .effect(() -> new MobEffectInstance(MobEffects.CONFUSION, 200, 1), 0.1f)
                .alwaysEat().build();

}

接着把上述效果使用.food添加到注册该物品的地方:

java 复制代码
public static final RegistryObject<Item> CANDY_STICK =
            ITEMS.register("candy_stick", () -> new Item(new Item.Properties().food(ModFoods.CANDY_STICK)));

运行测试

可以看到能够正常产生食物的效果。

相关推荐
浪子不回头4152 小时前
Triton学习笔记
笔记·学习
Hx_Ma162 小时前
mybatis练习2
java·数据库·mybatis
桂花很香,旭很美2 小时前
Anthropic Agent 工程实战笔记(三)上下文与记
笔记·架构·language model
山北雨夜漫步2 小时前
MQ消息队列
java·开发语言
毕设源码-邱学长2 小时前
【开题答辩全过程】以 基于Web的小型宾馆客房管理系统为例,包含答辩的问题和答案
java
我的xiaodoujiao2 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 51--CI/CD 4--推送本地代码到Git远程仓库
python·学习·测试工具·ci/cd·pytest
babe小鑫2 小时前
大专政务大数据应用专业学习数据分析的价值分析
大数据·学习·政务
Zhu_S W2 小时前
EasyExcel:让Excel操作变得简单优雅
java·前端
爱学习的小可爱卢2 小时前
JavaSE基础-Java字符串转整数与拼接实战指南
java·开发语言