[原创][1.20.1][EidolonJS开发实录]基于链式调用达成在Schema注册中使用回调函数构建配方
灵感大王
2
帖子
2
发布者
20
浏览
1
关注中
-
项目地址:PickAID/EidolonJS: as the name says
在为Eidolon中的坩埚注册相应的Schema时,我发现了一个问题:
坩埚配方的输入并不能够使用简单的RecipeKey添加,相关的代码如下:
public static class Step { public final List<Ingredient> matches = new ArrayList<>(); public final int stirs; public Step(int stirs, List<Ingredient> matches) { this.stirs = stirs; this.matches.addAll(matches); } }
而最初一版的RecipeKey与Schema设计如下:
public interface CrucibleSchema { RecipeKey<OutputItem> OUTPUT = ItemComponents.OUTPUT_ID_WITH_COUNT.key("result"); RecipeKey<OutputItem[]> OUTPUT = ItemComponents.OUTPUT_ARRAY.key("result"); RecipeComponentBuilder STEP_BUILDER = new RecipeComponentBuilder(2) .add(NumberComponent.INT.key("stirs").optional(1)) .add(NumberComponent.INT.key("stirs").defaultOptional()) .add(ItemComponents.INPUT_ARRAY.key("items")); RecipeKey<RecipeComponentBuilderMap[]> STEPS = STEP_BUILDER.inputRole().asArray().key("steps"); RecipeSchema SCHEMA = new RecipeSchema(OUTPUT, STEPS);
进行注册后简单对代码进行测试:
ServerEvents.recipes(event => { event.recipes.eidolon.crucible("2x stone", [ {stirs: 1, items: ["#forge:dusts/redstone", "2x eidolon:soul_shard"]}, {items: ["#forge:dusts/redstone", "eidolon:soul_shard"]}, {stirs: 1}, {stirs: 3} ]) }
配方的确可以正常注册,但是可以看到,Step部分的格式与json几乎一致,不够优雅。
于是笔者开始尝试在RecipeKey中使用回调函数,然而失败了(也有可能是我菜),
而我在阅读了SummoningRitual的源代码之后(AlmostReliable/summoningrituals at 1.20.1-forge)有了不一样的想法。
或许可以使用链式调用形式的配方,并在链式调用的方法中使用回调函数。
完整的代码实现如下://篇幅原因省去import //CrucibleSchema.java public interface CrucibleSchema { @FunctionalInterface interface StepBuilderCallback { void apply(StepBuilderJS builder); } class CrucibleRecipeJS extends RecipeJS { public CrucibleRecipeJS steps(StepBuilderCallback callback) { var builder = new StepBuilderJS(); callback.apply(builder); setValue(STEPS, builder.getStepList().toArray(CrucibleRecipe.Step[]::new)); return this; } } RecipeKey<CrucibleRecipe.Step[]> STEPS = new RecipeComponent<CrucibleRecipe.Step[]>() { @Override public Class<?> componentClass() { return CrucibleRecipe.Step[].class; } @Override public JsonElement write(RecipeJS recipe, CrucibleRecipe.Step[] value) { if (value == null) { return null; } JsonArray stepsArray = new JsonArray(); for (CrucibleRecipe.Step step : value) { JsonObject stepObj = new JsonObject(); // Add stirs stepObj.addProperty("stirs", step.stirs); // Add items if (!step.matches.isEmpty()) { JsonArray itemsArray = new JsonArray(); for (Ingredient ingredient : step.matches) { itemsArray.add(ingredient.toJson()); } stepObj.add("items", itemsArray); } stepsArray.add(stepObj); } return stepsArray; } @Override public CrucibleRecipe.Step[] read(RecipeJS recipe, Object from) { if (from instanceof JsonElement) { JsonArray stepsArray = ((JsonElement) from).getAsJsonArray(); List<CrucibleRecipe.Step> steps = new ArrayList<>(); for (JsonElement element : stepsArray) { if (!element.isJsonObject()) { throw new JsonSyntaxException("Each step must be a JSON object"); } JsonObject stepObj = element.getAsJsonObject(); int stirs = stepObj.has("stirs") ? stepObj.get("stirs").getAsInt() : 0; List<Ingredient> ingredients = new ArrayList<>(); if (stepObj.has("items")) { JsonArray itemsArray = stepObj.getAsJsonArray("items"); for (JsonElement item : itemsArray) { ingredients.add(Ingredient.fromJson(item)); } } steps.add(new CrucibleRecipe.Step(stirs, ingredients)); } return steps.toArray(CrucibleRecipe.Step[]::new); } else { throw new JsonSyntaxException("Each step must be a JSON object"); } } }.key("steps").noBuilders(); RecipeKey<OutputItem> OUTPUT = ItemComponents.OUTPUT.key("result").noBuilders(); RecipeSchema SCHEMA = new RecipeSchema(CrucibleRecipeJS.class, CrucibleRecipeJS::new, OUTPUT, STEPS).constructor(((recipe, schemaType, keys, from) -> { recipe.setValue(OUTPUT, from.getValue(recipe, OUTPUT)); recipe.setValue(STEPS, new CrucibleRecipe.Step[0]); }), OUTPUT); } //StepBuilderJS.java public class StepBuilderJS { List<CrucibleRecipe.Step> stepList = new ArrayList<>(); List<Ingredient> item = new ArrayList<>(); int stirs = 0; public StepBuilderJS stirs(int count) { stirs = count; return this; } public StepBuilderJS stirs() { return stirs(1); } public StepBuilderJS items(Ingredient... items) { item.addAll(List.of(items)); return this; } public void step(int count, Ingredient... items) { var step = new CrucibleRecipe.Step(count, item); stepList.add(step); } public void build(){ var step = new CrucibleRecipe.Step(stirs, item); stepList.add(step); stirs(0); item.clear(); } @HideFromJS public List<CrucibleRecipe.Step> getStepList() { return stepList; } }
最终实现的效果如下:
event.recipes.eidolon.crucible("2x stone").steps((step) => { step.stirs(1).items("#forge:dusts/redstone", "eidolon:soul_shard").build() step.items("#forge:dusts/redstone","eidolon:soul_shard").build() step.stirs(3).build() })
或许直接使用链式调用传入每一步的设计更为直接,但是出于个人偏好,笔者仍然选择使用回调函数的形式处理这部分。
(//TODO 代码详解施工中)