From ce632392f8d4a2213bb08364ca492e6cbbd9b106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=E9=9B=A8=E8=90=BD?= Date: Sun, 2 Jun 2024 18:20:56 +0800 Subject: [PATCH] Add: data generator. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 秋雨落 --- Documents/documents.tree | 13 +- Documents/redirection-rules.xml | 24 ++ .../dev-sc-block-BlockWithBlockEntity.md | 12 - .../topics/dev-sc-block-ILootableBlock.md | 17 - Documents/topics/dev-sc-block-chests.md | 64 ---- Documents/topics/dev-sc-block-crop-blocks.md | 203 ---------- Documents/topics/dev-sc-block-crops.md | 43 --- Documents/topics/dev-sc-data-generator.md | 355 ++++++++++++++++++ Documents/topics/dev-sc-gui.md | 3 + .../topics/dev-sc-utility-BlockLootables.md | 3 - .../topics/dev-sc-utility-BlockShapeHelper.md | 7 + .../topics/dev-sc-utility-VoxelShapeHelper.md | 7 - Documents/topics/starter.md | 4 +- 13 files changed, 395 insertions(+), 360 deletions(-) delete mode 100644 Documents/topics/dev-sc-block-BlockWithBlockEntity.md delete mode 100644 Documents/topics/dev-sc-block-ILootableBlock.md delete mode 100644 Documents/topics/dev-sc-block-chests.md delete mode 100644 Documents/topics/dev-sc-block-crop-blocks.md delete mode 100644 Documents/topics/dev-sc-block-crops.md create mode 100644 Documents/topics/dev-sc-data-generator.md create mode 100644 Documents/topics/dev-sc-gui.md delete mode 100644 Documents/topics/dev-sc-utility-BlockLootables.md create mode 100644 Documents/topics/dev-sc-utility-BlockShapeHelper.md delete mode 100644 Documents/topics/dev-sc-utility-VoxelShapeHelper.md diff --git a/Documents/documents.tree b/Documents/documents.tree index db2a0c3..5161b6c 100644 --- a/Documents/documents.tree +++ b/Documents/documents.tree @@ -15,18 +15,11 @@ - - - - - - - - + + - - + diff --git a/Documents/redirection-rules.xml b/Documents/redirection-rules.xml index c7e960a..a9e8cc6 100644 --- a/Documents/redirection-rules.xml +++ b/Documents/redirection-rules.xml @@ -6,4 +6,28 @@ page.html --> + + Created after removal of "ILootableBlock" from SinoSeries Document + dev-sc-block-ILootableBlock.html + + + Created after removal of "Crop" from SinoSeries Document + dev-sc-block-crops.html + + + Created after removal of "作物" from SinoSeries Document + dev-sc-block-crop-blocks.html + + + Created after removal of "带有 BlockEntity 的 Block" from SinoSeries Document + dev-sc-block-BlockWithBlockEntity.html + + + Created after removal of "箱子" from SinoSeries Document + dev-sc-block-chests.html + + + Created after removal of "BlockLootables" from SinoSeries Document + dev-sc-utility-BlockLootables.html + \ No newline at end of file diff --git a/Documents/topics/dev-sc-block-BlockWithBlockEntity.md b/Documents/topics/dev-sc-block-BlockWithBlockEntity.md deleted file mode 100644 index b8d4c06..0000000 --- a/Documents/topics/dev-sc-block-BlockWithBlockEntity.md +++ /dev/null @@ -1,12 +0,0 @@ -# 带有 BlockEntity 的 Block - -对于带有 `BlockEntity` 的 `Block`,`SinoCore` 提供 `AbstractEntityBlock` 类作为 `Block` 的基类。 - -`AbstractEntityBlock` 是一个抽象类,要求一个 `BlockEntity` 类型的泛型,直接继承自 `BaseEntityBlock`。程序运行时, -通过反射获取到该泛型的具体类型以识别所需 `BlockEntity` 类型。与原版 `BaseEntityBlock` 相比,该类有以下特性: - -- 实现了 `newBlockEntity` 方法,默认通过 `BlockEntityType` 创建 `BlockEntity` 对象 -- 重写了 `getTicker` 方法。只需要在该类对应的 `BlockEntity` 上实现 `BlockEntityTicker` 接口即可实现 `BlockEntity` 每 tick 执行任务 -- 重写了 `getListener` 方法。只需要在该类对应的 `BlockEntity` 上实现 `GameEventListener` 接口即可实现对原版游戏事件的监听 -- 重写了 `use` 方法,只需要在该类对应的 `BlockEntity` 上实现 `MenuProvider` 接口即可实现右键方块打开对应的 GUI -- 重写了 `getRenderShape` 方法,默认 `RenderShape.MODEL`,使之直接使用方块材质而非 `TER` diff --git a/Documents/topics/dev-sc-block-ILootableBlock.md b/Documents/topics/dev-sc-block-ILootableBlock.md deleted file mode 100644 index c6a0d46..0000000 --- a/Documents/topics/dev-sc-block-ILootableBlock.md +++ /dev/null @@ -1,17 +0,0 @@ -# ILootableBlock - -`ILootableBlock` 接口用于在 `Block` 类中定义掉落物。该接口有且仅有一个方法 `createLootBuilder` 用于生成方块掉落物的战利品表。 - -```java -/** - * 在自定义的 Block 类中实现,用于 DataProvider 自动注册其掉落物 - */ -public interface ILootableBlock { - - LootTable.Builder createLootBuilder(BlockLootables helper); -} -``` - -`BlockLootables` 工具可以辅助创建各种常见用于掉落物的战利品表。其内容与 `BlockLootSubProvider` 类提供的工具方法一致。 - -在配合 `AbstructLootTableProvider` 使用时,可自动根据该方法生成方块战利品。详见数据生成部分相关内容。 \ No newline at end of file diff --git a/Documents/topics/dev-sc-block-chests.md b/Documents/topics/dev-sc-block-chests.md deleted file mode 100644 index b0ef352..0000000 --- a/Documents/topics/dev-sc-block-chests.md +++ /dev/null @@ -1,64 +0,0 @@ -# 箱子 - -箱子类是实现树时的一种副产物,早期伴随树而注册,后来转为独立实现和注册。 -每个箱子都有对应的 `BlockEntity`,方块对应的 `BlockEntity` 可直接通过构造函数创建。 - -| 箱子类型 | Block 类 | BlockEntity 类 | -|:----:|:-----------------------:|:-------------------------------:| -| 普通箱子 | `ChestBlockBase` | `SimpleChestBlockEntity` | -| 陷阱箱 | `TrappedChestBlockBase` | `SimpleTrappedChestBlockEntity` | - -`ChestBlockBase#verifyTexture` 可用于校验箱子纹理是否存在。如果不存在则会在 log 中输出缺失的纹理。一般在注册 `DataProvider` -的事件中调用该函数。 - -## 物品 - -箱子方块对应的物品使用 `BaseChestItem` 创建。该类是一个抽象类,继承自 `BlockItem`,可以通过 `create` 工厂方法创建, -或实现 `getBlockEntityType` 方法返回对应方块的 `BlockEntityType` 实例。 - -`BaseChestItem` 实现了用于箱子的发射器行为和物品渲染,并允许通过配方的形式设置燃烧时间。 - -## 渲染 - -与箱子方块配套的,还包括一系列客户端使用的渲染类: - -- 用于 BlockEntity 方块渲染的 `BaseChestRenderer` 类,使用时直接在 `EntityRenderersEvent.RegisterRenderers` 事件中注册即可。 - - 该类继承自 `ChestRenderer`,在构造函数中还需要传入额外参数以确定实体纹理所在位置: - - ```Java - public BaseChestRenderer(BlockEntityRendererProvider.Context context, ResourceLocation name, boolean isTrapped) { - // ... - } - ``` - - `ResourceLocation name` 决定了箱子纹理名称的主要部分,`boolean isTrapped` 决定是否添加 `trapped_` 前缀,表示陷阱箱。 - - 箱子纹理保存于 `assets/${name.namespace}/textures/entity/chest` 中,普通箱子需要以下几种材质: - - - `${name.path}.png` - - `${name.path}_left.png` - - `${name.path}_right.png` - - 陷阱箱需要以下几种材质: - - - `trapped_${name.path}.png` - - `trapped_${name.path}_left.png` - - `trapped_${name.path}_right.png` - - 不带有后缀的纹理表示单一箱子的纹理,带有 `_left` 后缀表示大箱子左半面的纹理,带有 `_right` 后缀表示大箱子右半面的纹理。 - -- 用于物品渲染的 `BaseChestItemRenderer` 类。该类在 `BaseChestItem#initializeClient` 中自动创建,无需手动注册或创建。 - -## 注册与使用流程 - -1. 通过 `DeferredRegister` 注册用于箱子的 `Block`, `BlockEntity` 和 `Item` -2. 监听 `EntityRenderersEvent.RegisterRenderers` 事件(bus=MOD, value=CLIENT), -使用 `RegisterRenderers#registerBlockEntityRenderer` 方法注册用于方块渲染的 `BaseChestRenderer` -3. 添加 DataProvider 并执行 `runData`,根据 log 提示补全丢失的纹理 - - 通过 `AbstractBlockTagsProvider#chest` 添加默认方块 Tag,`AbstractItemTagsProvider#chest` 添加默认物品 Tag - - 通过 `AbstructLootTableProvider#addBlock` 添加掉落物表 - - 通过 `AbstractAutoBlockStateProvider#chest` 添加方块模型支持 - - 通过 `AbstractItemTagsProvider#chest` 添加物品模型支持 - - 通过 `AbstractRecipeProvider#chest` 添加物品配方 - - 添加对应语言 Provider diff --git a/Documents/topics/dev-sc-block-crop-blocks.md b/Documents/topics/dev-sc-block-crop-blocks.md deleted file mode 100644 index 791444a..0000000 --- a/Documents/topics/dev-sc-block-crop-blocks.md +++ /dev/null @@ -1,203 +0,0 @@ -# 作物 - -作物基类为 `SimpleCropBlock`,该类表示一个一般情况下的单方块作物。该类继承自 `CropBlock`,与 `CropBlock` 相比具有以下特点: - -- 可自定义作物生长阶段 -- 可配合 `AbstructLootTableProvider` 自动生成对应掉落物的战利品表 -- 可配合 `AbstractAutoBlockStateProvider` 自动生成对应方块模型 - -`SimpleCropBlock` 类不是一个抽象类。一般情况下,该类开箱即用。其两个构造函数如下: - -```java -public SimpleCropBlock(Supplier> crop, int age, int minSeedCount, int maxSeedCount, int minCropCount, int maxCropCount) { - // ... -} - -public SimpleCropBlock(int age, int minCrop, int maxCrop) { - // ... -} -``` - -`age` 参数表示该作物的最大生长阶段。 第一个构造函数包含一个 `crop` 参数表示作物成熟后的产物,之后三个 `count` 参数则是种子和产物的掉落数量, -影响成熟后掉落物数量,该构造函数用于创建类似原版甜菜、马铃薯等带有种子的作物。 第二个构造只需要额外给两个 `count` 参数即可,表示成熟后掉落物数量。 -此构造创建的是类似原版胡萝卜等成熟产物与种子为同一个物品的作物。 - -如果一个作物没有特殊需求,该类中最常被重写的方法是 `VoxelShape getShape(BlockState, BlockGetter, BlockPos, CollisionContext)`。 -该方法用于修改不同情况下作物的碰撞体积。 - -## 双层作物 - -双层作物,即某个作物由上下两个方块组成,其基类为 `DoubleCropBlock`。该类直接继承自 `DoublePlantBlock`,并实现了 `Crop` 接口。默认情况下, -该类对应的作物 `BlockState` 对象存在对应 age 属性和 `HALF=BlockStateProperties.DOUBLE_BLOCK_HALF` 属性。 - -在默认双层作物的掉落物中,只破坏上方方块不会掉落任何物品,只有下方方块会掉落固定一个种子和对应成熟时的额外掉落。 - -与 `SimpleCropBlock` 不同的是,该类为一个抽象类。使用时至少需要实现以下额外的方法: - -- `VoxelShape getShape(BlockState, BlockGetter, BlockPos, CollisionContext)`:获取方块的模型大小 -- `boolean mayPlaceOn(BlockState pState, BlockGetter pLevel, BlockPos pPos)`:对应物品可以放置在怎样的方块上。给定的 `pPos` - 和 `pState` 是下方方块的位置,上方方块不会触发该方法的判断,因此该方法往往需要同时判断当前位置和上方位置两个位置是否符合标准 -- `boolean canSurviveLower(BlockState, LevelReader, BlockPos)`:下方方块是否可以放置在对应位置上。 这里不用判断上方方块是否符合要求。 -该方法将在 `canSurvive` 方法中调用。 - -## 实例 - -创建一个完整的作物方块通常包括以下几步,以 `SinoFoundation` 中的茄子作物为例: - -1. 如果作物有产物,先创建和注册对应产物的物品。否则不需要。 - - ```java - // SFDItems 类中 - public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, SinoFoundation.MODID); - - public static final RegistryObject EGGPLANT = ITEMS.register("eggplant", - () -> new Item(new Item.Properties().food(new FoodProperties.Builder().nutrition(1).saturationMod(0.2f).build()))); - - // 在 Mod 主类中调用 - public static void register(IEventBus bus) { - ITEMS.register(bus); - } - ``` - -2. 创建和注册对应方块,有时也需要新建对应的类。 - - ```java - // SFDBlocks 类中 - public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, SinoFoundation.MODID); - - // 茄子的生长具有 7 个阶段,成熟后额外掉落 0-1 个种子和 2-5 个茄子 - public static final RegistryObject EGGPLANT_PLANT = BLOCKS.register("eggplant", - () -> new CropBlockWith4Shape<>(SFDItems.EGGPLANT, 7, 0, 1, 2, 5)); - - // 在 Mod 主类中调用 - public static void register(IEventBus bus) { - BLOCKS.register(bus); - } - ``` - - 其中,自定义的方块类 `CropBlockWith4Shape` 为一个只有 4 种方块大小的 `SimpleCropBlock` 子类: - - ```java - public class CropBlockWith4Shape extends SimpleCropBlock { - - // 四种方块大小 - protected static final VoxelShape[] SHAPES = new VoxelShape[]{ - Block.box(0, 0, 0, 16, 2, 16), - Block.box(0, 0, 0, 16, 5, 16), - Block.box(0, 0, 0, 16, 7, 16), - Block.box(0, 0, 0, 16, 9, 16), - }; - - public CropBlockWith4Shape(Supplier> crop, int age, int minSeedCount, int maxSeedCount, int minCropCount, int maxCropCount) { - super(crop, age, minSeedCount, maxSeedCount, minCropCount, maxCropCount); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { - int age = state.getValue(getAgeProperty()); - // 选择不同的放块大小 - int stage = age / ((getMaxAge() + 1) / 4); - return SHAPES[stage]; - } - } - ``` - -3. 创建和注册方块对应的物品。通常来说方块对应的物品就是该作物的种子。 - - ```java - // SFDBlockItems 类中 - public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, SinoFoundation.MODID); - - public static final RegistryObject EGGPLANT_SEED = ITEMS.register("eggplant_seed", - () -> new ItemNameBlockItem(SFDBlocks.EGGPLANT_PLANT.get(), new Item.Properties())); - - // 在 Mod 主类中调用 - public static void register(IEventBus bus) { - ITEMS.register(bus); - } - ``` - -4. 创建并注册一个继承自 `AbstructLootTableProvider` 类的 `DataProvider`,在其中注册该方块。通常来说,直接注册对应的 `DeferredRegister` - 相当于注册该 `DeferredRegister` 中的所有方块。该 `DataProvider` 用于生成作物的掉落物表。 - - ```java - @Mod.EventBusSubscriber(modid = SinoFoundation.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) - public class SFDData { - @SubscribeEvent - public static void onGatherData(GatherDataEvent event) { - DataGenerator gen = event.getGenerator(); - PackOutput output = gen.getPackOutput(); - gen.addProvider(true, new SFDBlockLootTableProvider(output, SinoFoundation.MODID)); - } - } - ``` - - 在 `SFDBlockLootTableProvider` 中,我们只需要添加对应的 `Block` 即可。具体掉落内容我们已经在 `SFDBlocks.EGGPLANT_PLANT` 方块中配置了。 - - ```java - public class SFDBlockLootTableProvider extends AbstractAutoBlockStateProvider { - - public SFDBlockLootTableProvider(PackOutput output, String modid) { - super(output, modid); - } - - @Override - public void getTables(List tables) { - // 导入 DeferredRegister 中的所有方块 - addBlocks(SFDBlocks.BLOCKS); - } - } - ``` - -5. 创建并注册继承自 `AbstractAutoBlockStateProvider` 类的 `DataProvider`,并在其中注册作物方块。 - - ```java - @Mod.EventBusSubscriber(modid = SinoFoundation.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) - public class SFDData { - @SubscribeEvent - public static void onGatherData(GatherDataEvent event) { - DataGenerator gen = event.getGenerator(); - ExistingFileHelper exHelper = event.getExistingFileHelper(); - PackOutput output = gen.getPackOutput(); - - gen.addProvider(true, new SFDBlockLootTableProvider(output, SinoFoundation.MODID)); - gen.addProvider(true, new SFDBlockStateProvider(output, SinoFoundation.MODID, exHelper, SFDBlocks.BLOCKS)); - } - } - ``` - - `AbstractAutoBlockStateProvider` 类内置一个 `crop()` 方法可快捷添加作物,适用于任何实现 `Crop` 接口的 `Block` 类对象。 - - ```java - public class SFDBlockStateProvider extends AbstractAutoBlockStateProvider { - - @SafeVarargs - public SFDBlockStateProvider(PackOutput output, String modId, ExistingFileHelper existingFileHelper, DeferredRegister... deferredRegisters) { - super(output, modId, existingFileHelper, deferredRegisters); - } - - @Override - protected void registerBlockStatesAndModels() { - crop(SFDBlocks.EGGPLANT_PLANT); - } - } - ``` - - 此时如果运行一次 `runData`,可以看见缺失的所有材质。生成器会自动判断是否存在 `BlockStateProperties.DOUBLE_BLOCK_HALF` 属性,若存在则 - 认为是双层作物,其材质命名规则为: - - - 上层作物方块: `作物方块名_stage_top_作物age属性` - - 下层作物方块: `作物方块名_stage_bottom_作物age属性` - - 单层作物材质命名规则为:`作物方块名_stage_作物age属性`,如茄子的 age 属性值为 0-8,则其材质包括以下几个: - - - `resources/assets/sinofoundation/textures/block/eggplant_stage_0.png` - - `resources/assets/sinofoundation/textures/block/eggplant_stage_1.png` - - `resources/assets/sinofoundation/textures/block/eggplant_stage_2.png` - - `resources/assets/sinofoundation/textures/block/eggplant_stage_3.png` - - `resources/assets/sinofoundation/textures/block/eggplant_stage_4.png` - - `resources/assets/sinofoundation/textures/block/eggplant_stage_5.png` - - `resources/assets/sinofoundation/textures/block/eggplant_stage_6.png` - - `resources/assets/sinofoundation/textures/block/eggplant_stage_7.png` - -6. 实现并注册 `ItemModelProvider`,`LanguageProvider` 等其他配件,并运行 `runData` 任务,根据输出准备好对应材质 diff --git a/Documents/topics/dev-sc-block-crops.md b/Documents/topics/dev-sc-block-crops.md deleted file mode 100644 index 0a23f44..0000000 --- a/Documents/topics/dev-sc-block-crops.md +++ /dev/null @@ -1,43 +0,0 @@ -# Crop - - -`Crop` 接口表示一个方块为作物,其泛型类型 T 表示成熟后产物物品类型。该接口由以下几分组成: - -- `BonemealableBlock`:该接口表示对应方块可以对骨粉产生响应。接口对其实现为:作物生长周期为 3 时随即增加 0-2,否则增加 2-5。 -- `ILootableBlock` 接口与相关方法:该接口定义了作物的默认掉落物。新增了一个 `createLootBuilder` 方法的默认实现,用于根据作物生成战利品表。该方法的默认行为包括: - - 1. 无论作物是否成熟,固定掉落一个种子 - 2. 若作物成熟,额外随机掉落 \[minSeedCount, maxSeedCount\] 颗种子和 \[minCropCount, maxCropCount\] 个作物。 - -```java -default LootTable.Builder createLootBuilder(BlockLootables helper, - int minSeedCount, int maxSeedCount, - int minCropCount, int maxCropCount) { - // ... -} -``` - -- 作物本身的一些属性和成长行为。这些方法多与 `CropBlock` 的相关方法重合。主要用于规范实际实现了作物的行为,但不继承自 `CropBlock` 类的作物,如双层作物等。相关方法主要包括: - - - `getBaseSeedId`, `getCrop`:获取作物对应种子和成熟后产物的对应物品。默认 `getBaseSeedId` 返回的是当前方块对应的物品。 - - `getAgeProperty`, `getMaxAge`:获取标记作物成长的 `Property` 和作物最大成长阶段。 - - `getAge`, `isMaxAge`, `getStateForAge`:`BlockState` 与作物成长阶段的互操作,用于获取和设置对应作物的阶段及检验是否成熟。 - - `growCrops`:实现作物成长。 - - `getGrowthSpeed`:主要用于在 `randomTick` 中调用,计算自然条件下作物的生长因子,默认使用与 `CropBlock` 相同的实现,其返回值应在 \[0, 6\] 范围内。参考 Minecraft 和 Forge 的默认实现,一个单方块作物 `randomTick` 方法实现方式为: - -```java -@Override -public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { - if (!level.isAreaLoaded(pos, 1)) - return; // Forge: prevent loading unloaded chunks when checking neighbor's light - if (!isMaxAge(state) && level.getRawBrightness(pos, 0) >= 9) { - float f = getGrowthSpeed(level, pos); - if (ForgeHooks.onCropsGrowPre(level, pos, state, random.nextInt((int) (25.0F / f) + 1) == 0)) { - growCrops(level, pos, state, false); - ForgeHooks.onCropsGrowPost(level, pos, state); - } - } -} -``` - -- 用于获取生长阶段的 `Property` 对象的静态工具方法 `getAgeProperties`。通过该方法可以获取官方定义的 8 种成长阶段属性,也可以唯一的创建和获取任意长度的属性。 diff --git a/Documents/topics/dev-sc-data-generator.md b/Documents/topics/dev-sc-data-generator.md new file mode 100644 index 0000000..8abd5bb --- /dev/null +++ b/Documents/topics/dev-sc-data-generator.md @@ -0,0 +1,355 @@ +# 数据生成 + +*注:DataProvider 仅在 Forge 端运行,但在 common 子项目中创建和注册* + +## 使用 + +### IDataGenContext + +`IDataGenContext` 类提供创建 `DataProvider` 所需的必要信息,包括 `ModId`,`PackOutput`,`HolderLookup.Provider` 等。 + +## 注册 + +数据生成通过 `RegistryManager` 注册: + +```java +IDataProviderRegister register = RegistryManager.obtainDataProvider(MODID); + +register.put((context) -> { + // context: IDataGenContext + // 返回一个 DataProvider +}); +``` + +## 内置 Data Provider + +- `AbstractAdvancementProvider`:注册新进度 + +- `AbstractBiomeModifierProvider`:注册 `BiomeModifier`,允许向已有维度添加自定义世界生成 + +- `AbstractBlockTagsProvider`,`AbstractItemTagsProvider`:方块和物品 Tag + +- `AbstractLanguageProvider`:语言文件 + +- `AbstractLootTableProvider`:方块、实体、钓鱼等奖励箱列表 + +- `AbstractRecipeProvider`:各种配方 + +- `AbstractDatapackBuiltinEntriesProvider`:Minecraft 原版数据包对象 + +- `AbstractCodecProvider`:其他使用 `Codec` 创建的文件 + +- `AbstractItemModelProvider`:物品模型生成 + +## 继承自 Forge 提供的 Provider + +可以直接在 forge 子模块的 `DataProvider`。在 forge 子模块,创建一个 `ForgeProvider.IForgeProviders` 接口的实现类,并在 common 子模块注册一个 `ForgeProvider` 类型的 `DataProvider` 即可。 + + +- 无论一个 Mod 中有多少个 `IForgeProviders`,只需要注册一个 `ForgeProvider` 即可 + +- `IForgeProviders` 实现类必须有一个无参构造 + + + +```java +// forge 子模块 +public class TestForgeProviders implements ForgeProvider.IForgeProviders { + + @Override + public String getModId() { + return SinoTest.MODID; + } + + @Override + public List allProviders(IDataGenContext context) { + return List.of(new TestBlockStateProvider(context)); + } +} + +// common 子模块 +public class TestDatagen { + + public static void registerProvider() { + IDataProviderRegister register = + RegistryManager.obtainDataProvider(SinoTest.MODID); + register.put(ForgeProvider::new); + } +} +``` + +## 自定义 Provider + +在没有特殊需求的情况下,直接实现 `DataProvider` 接口及其各方法即可。 + +### Forge DataProvider + +Forge 本身提供了很多辅助创建 Mod 的工具类和预设 `DataProvider`。可以在 Forge 子模块创建 `DataProvider`,并在 common 包使用。 + +由于 Architectury 项目的依赖关系,要想使用 Forge 包的 `DataProvider`,需要实现以下几个类: + +- `ForgeDataProviderBase`:在 common 包使用的 `DataProvider`。其中 `generateData` 方法用于收集需要生成的数据 + +- `ProviderDelegateBase`:持有 Forge 端 `DataProvider` 的代理类,common 端添加数据的工具类 + +Forge 预定义的 `DataProvider` 有两种情况: + +- 类中有一个方法单独用于收集数据。大多数 `DataProvider` 都是这种 + +- 构造函数中传入全部数据的集合,如 `ForgeAdvancementProvider` 和 `DatapackBuiltinEntriesProvider` + +两种情况分别对应 `ForgeDataProviderBase` 类中 `generateData` 方法的调用位置不同。前者需要在 Forge 端具体 `DataProvider` 的实现类中的对应方法中调用,后者则不需要手动调用,但需要使用 `DataProviderBuilderBase` 类创建对应的 `DataProvider`。 + +#### 例:独立方法收集数据 + +在独立的方法中收集数据的一个例子是 `LanguageProvider`。在 Forge 中,该类提供一个 `addTranslations()` 方法用于收集所有语言信息。 + +首先,是 common 子模块的工作。common 子模块无法访问 forge 子模块的细节,因此 `DataProvider` 的信息在 common 包是无法得知的。 + +创建对应的代理子类继承 `ProviderDelegateBase` 类。使用接收 `DataProvider` 的构造函数。在这个类中,我们可以添加用于添加数据的辅助方法。 + +```java +public abstract class LanguageProviderDelegateBase + extends ProviderDelegateBase { + + protected LanguageProviderDelegateBase(DataProvider provider) { + super(provider); + } + + public abstract void addBlock(Block key, String name); + + public abstract void addItem(Item key, String name); + + public abstract void add(String key, String value); +} +``` + +之后,创建 common 包中使用的 `DataProvider`,实现 `ForgeDataProviderBase` 类,其中泛型类型就是刚刚创建的 `LanguageProviderDelegateBase` 类。 + +在这里我们需要一个 `@ExpectPlatform` 的方法,通过该方法将构造函数中创建代理类 `LanguageProviderDelegateBase` 的工作传递给 Forge 平台,并在其他平台直接抛出异常即可。 + +```java +public abstract class AbstractLanguageProvider + extends ForgeDataProviderBase { + + public AbstractLanguageProvider(IDataGenContext context, String locale) { + super(createDelegate(context, locale)); + } + + @ExpectPlatform + public static LanguageProviderDelegateBase createDelegate(IDataGenContext context, String locale) { + throw new AssertionError(); + } +} +``` + +至此,common 子模块的准备工作便完成了。下面的工作在 forge 子模块完成: + +在 Forge 端,我们首先要创建一个 `DataProvider` 类 `LanguageProvider` 的实现类。在这个类中,我们需要传入一个代理类 `LanguageProviderDelegateBase` 的实例,并在正确的地方调用 `generateData()` 方法。 + +```java +public class ForgeLanguageProviderImpl extends LanguageProvider { + private ForgeLanguageProviderDelegateImpl delegate; + + ForgeLanguageProviderImpl(IDataGenContext context, String locale) { + super(context.getOutput(), context.getModId(), locale); + } + + public void setDelegate(ForgeLanguageProviderDelegateImpl delegate) { + this.delegate = delegate; + } + + @Override + protected void addTranslations() { + delegate.generateData(); + } +} +``` + +之后,创建代理类 `LanguageProviderDelegateBase` 的实现类。在这个类中,我们要做的事情是将数据传递给对应的 `DataProvider` 实现类,这个 `DataProvider` 可以从 `getForgeProvider()` 方法获取。 + +```java +public class ForgeLanguageProviderDelegateImpl + extends LanguageProviderDelegateBase { + + private final ForgeLanguageProviderImpl provider; + + public ForgeLanguageProviderDelegateImpl(IDataGenContext context, String locale) { + super(new ForgeLanguageProviderImpl(context, locale)); + provider = getForgeProvider(); + provider.setDelegate(this); + } + + @Override + public void addBlock(Block key, String name) { + provider.add(key, name); + } + + @Override + public void addItem(Item key, String name) { + provider.add(key, name); + } + + @Override + public void add(String key, String value) { + provider.add(key, value); + } +} +``` + +最后,遵循 Architectury 的要求,实现之前的 `@ExpectPlatform` 方法即可 + +```java +public class AbstractLanguageProviderImpl { + + public static LanguageProviderDelegateBase createDelegate(IDataGenContext context, String locale) { + return new ForgeLanguageProviderDelegateImpl(context, locale); + } +} +``` + +#### 例:构造函数收集数据 + +在构造函数中就收集完所有数据信息的一个例子是 `DatapackBuiltinEntriesProvider`。在构造函数中,该类接受一个 `RegistrySetBuilder` 对象,包含了所有可用数据。 + +首先,在 common 子模块中的准备与上个例子里使用的差不多,只是在创建代理类 `ProviderDelegateBase` 时使用接收 `DataProviderBuilderBase` 的构造函数。 + +```java +public abstract class DatapackProviderDelegateBase + extends ProviderDelegateBase { + + protected DatapackProviderDelegateBase(DataProviderBuilderBase builder) { + super(builder); + } + + public abstract void add(ResourceKey> type, Consumer> register); +} +``` + +```java +public abstract class AbstractDatapackBuiltinEntriesProvider + extends ForgeDataProviderBase { + + public AbstractDatapackBuiltinEntriesProvider(IDataGenContext context) { + super(createDelegate(context)); + } + + @ExpectPlatform + public static DatapackProviderDelegateBase createDelegate(IDataGenContext context) { + throw new AssertionError(); + } +} +``` + +在 forge 包中,还是先创建 `DataProvider` 实例 + +```java +public class ForgeDatapackBuiltinEntriesProviderImpl extends DatapackBuiltinEntriesProvider { + + private final String name; + + public ForgeDatapackBuiltinEntriesProviderImpl(IDataGenContext context, RegistrySetBuilder builder, String name) { + super(context.getOutput(), context.registriesFuture(), builder, Set.of(context.getModId())); + this.name = name; + } + + @Override + public String getName() { + return name; + } +} +``` + +之后,创建 `DataProvider` 的构造类,在其中需要提供创建方法和 `DataProvider` 名称,该名称将传递给 common 包的 `getName` 方法。 + +DataProviderBuilderBase 类的两个泛型分别是代理类实现类型和 `DataProvider` 的实现类型。 + +```java +public class ForgeDatapackProviderBuilderImpl + extends DataProviderBuilderBase { + + private final IDataGenContext context; + + public ForgeDatapackProviderBuilderImpl(IDataGenContext context) { + this.context = context; + } + + @Override + public ForgeDatapackBuiltinEntriesProviderImpl build(ForgeDatapackProviderDelegateImpl delegate) { + // todo 创建 DataProvider + } + + @Override + public String getDataProviderName() { + return "Registries: " + context.getModId(); + } +} +``` + +创建对应的代理类,在构造函数中创建对应的 Builder + +```java +public class ForgeDatapackProviderDelegateImpl extends DatapackProviderDelegateBase { + + // RegistryBootstrap 内 key 依赖于注册先后顺序 + private List entries = new ArrayList<>(); + private Entry lastEntry = null; + + public ForgeDatapackProviderDelegateImpl(IDataGenContext context) { + super(new ForgeDatapackProviderBuilderImpl(context)); + } + + public List getData() { + return entries; + } + + @Override + public void add(ResourceKey> type, Consumer> register) { + if (lastEntry != null && Objects.equals(lastEntry.type, type)) { + lastEntry.consumer = Functions.compose(lastEntry.consumer, register); + } else { + entries.add(lastEntry = new Entry(type, register)); + } + } + + public static class Entry { + final ResourceKey type; + Consumer consumer; + + public Entry(ResourceKey type, Consumer consumer) { + this.type = type; + this.consumer = consumer; + } + } +} +``` + +最后,实现前面的 `build` 方法和 `@ExpectPlatform` 方法 + +```java +public class AbstractDatapackBuiltinEntriesProviderImpl { + + public static DatapackProviderDelegateBase createDelegate(IDataGenContext context) { + if (context instanceof ForgeDataGenContextImpl impl) { + return new ForgeDatapackProviderDelegateImpl(impl); + } + throw new ClassCastException("Can't cast " + context + " to ForgeDataGenContextImpl at Forge Platform. " + + "Use SinoCorePlatform#buildDataGeneratorContext to create this context. " + + "Don't use context implemented yourself, because it contains different information in different platform"); + } +} +``` + +```java +public class ForgeDatapackProviderBuilderImpl + extends DataProviderBuilderBase { + // ... + + @Override + public ForgeDatapackBuiltinEntriesProviderImpl build(ForgeDatapackProviderDelegateImpl delegate) { + RegistrySetBuilder builder = new RegistrySetBuilder(); + delegate.getData().forEach(entry -> builder.add(entry.type, ctx -> entry.consumer.accept(ctx))); + return new ForgeDatapackBuiltinEntriesProviderImpl(context, builder, getDataProviderName()); + } +} +``` diff --git a/Documents/topics/dev-sc-gui.md b/Documents/topics/dev-sc-gui.md new file mode 100644 index 0000000..df10d75 --- /dev/null +++ b/Documents/topics/dev-sc-gui.md @@ -0,0 +1,3 @@ +# GUI + +Todo. diff --git a/Documents/topics/dev-sc-utility-BlockLootables.md b/Documents/topics/dev-sc-utility-BlockLootables.md deleted file mode 100644 index e9fc5cc..0000000 --- a/Documents/topics/dev-sc-utility-BlockLootables.md +++ /dev/null @@ -1,3 +0,0 @@ -# BlockLootables - -`BlockLootables` 工具类实现了原版 `BlockLootSubProvider` 类中提供的用于快捷创建战利品表的工具方法。 diff --git a/Documents/topics/dev-sc-utility-BlockShapeHelper.md b/Documents/topics/dev-sc-utility-BlockShapeHelper.md new file mode 100644 index 0000000..d0e45e2 --- /dev/null +++ b/Documents/topics/dev-sc-utility-BlockShapeHelper.md @@ -0,0 +1,7 @@ +# BlockShapeHelper + +`BlockShapeHelper` 工具类提供了操作 `VoxelShape` 的常见工具方法。 + +- `VoxelShape rotateX(VoxelShape shape)`:以X轴顺时针旋转 `shape`。 +- `VoxelShape rotateY(VoxelShape shape)`:以Y轴顺时针旋转`shape`。 +- `VoxelShape or(VoxelShape[] shapes)`:封装了 `Shapes.or(VoxelShape shape1, VoxelShape shape2)` 和 `Shapes.or(VoxelShape shape1, VoxelShape... others)` ,用于合并若干个 `VoxelShape`。 diff --git a/Documents/topics/dev-sc-utility-VoxelShapeHelper.md b/Documents/topics/dev-sc-utility-VoxelShapeHelper.md deleted file mode 100644 index 81464a7..0000000 --- a/Documents/topics/dev-sc-utility-VoxelShapeHelper.md +++ /dev/null @@ -1,7 +0,0 @@ -# VoxelShapeHelper - -`VoxelShapeHelper` 工具类提供了对 `VoxelShape` 的旋转支持: - -- `VoxelShape rotateHorizontal(VoxelShape shape, Direction from, Direction to)`:以竖直方向(Y轴)为轴从 `from` 到 `to` 朝向旋转 `shape` 。 -- `VoxelShape rotateClockwise(VoxelShape shape)`:以竖直方向(Y轴)为轴顺时针旋转 `shape` 1 次。 -- `VoxelShape rotateClockwise(VoxelShape shape, int times)`:以竖直方向(Y轴)为轴顺时针旋转 `shape` `times` 次。 diff --git a/Documents/topics/starter.md b/Documents/topics/starter.md index d114431..0087285 100644 --- a/Documents/topics/starter.md +++ b/Documents/topics/starter.md @@ -1 +1,3 @@ -# SinoSeries 华夏系列 Minecraft 模组 +# 华夏系列 Minecraft 模组 + +这是华夏系列 Minecraft 模组的官方中文文档。