项目地址: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;
}
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 {
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 {
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 代码详解施工中)