diff --git a/src/main/java/dev/latvian/mods/kubejs/command/KubeJSCommands.java b/src/main/java/dev/latvian/mods/kubejs/command/KubeJSCommands.java index 4f5d544d5..bb243e7ef 100644 --- a/src/main/java/dev/latvian/mods/kubejs/command/KubeJSCommands.java +++ b/src/main/java/dev/latvian/mods/kubejs/command/KubeJSCommands.java @@ -1,12 +1,15 @@ package dev.latvian.mods.kubejs.command; import com.google.common.base.Predicate; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.serialization.JsonOps; import dev.latvian.mods.kubejs.CommonProperties; import dev.latvian.mods.kubejs.KubeJS; import dev.latvian.mods.kubejs.KubeJSPaths; @@ -15,20 +18,24 @@ import dev.latvian.mods.kubejs.net.DisplayClientErrorsPayload; import dev.latvian.mods.kubejs.net.DisplayServerErrorsPayload; import dev.latvian.mods.kubejs.net.ReloadStartupScriptsPayload; +import dev.latvian.mods.kubejs.script.ConsoleJS; import dev.latvian.mods.kubejs.script.KubeJSContext; import dev.latvian.mods.kubejs.script.ScriptType; import dev.latvian.mods.kubejs.script.data.ExportablePackResources; import dev.latvian.mods.kubejs.server.BasicCommandKubeEvent; import dev.latvian.mods.kubejs.server.DataExport; +import dev.latvian.mods.kubejs.util.JsonUtils; import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.commands.arguments.DimensionArgument; import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.commands.arguments.ResourceKeyArgument; import net.minecraft.commands.arguments.ResourceLocationArgument; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.HoverEvent; @@ -229,6 +236,13 @@ public static void register(CommandDispatcher dispatcher) { .executes(ctx -> eval(ctx.getSource(), StringArgumentType.getString(ctx, "code"))) ) ); + + cmd.then(Commands.literal("generate-recipe-schema-json") + .requires(spOrOP) + .then(Commands.argument("recipe-type", ResourceKeyArgument.key(Registries.RECIPE_SERIALIZER)) + .executes(ctx -> generateRecipeSchemaJson(ctx.getSource(), ctx.getArgument("recipe-type", ResourceKey.class))) + ) + ); } var cmd1 = dispatcher.register(cmd); @@ -548,4 +562,49 @@ private static int eval(CommandSourceStack source, String code) { cx.evaluateString(cx.topLevelScope, code, "eval", 1, null); return 1; } + + private static int generateRecipeSchemaJson(CommandSourceStack source, ResourceKey id) { + var storage = source.getServer().getServerResources().managers().kjs$getServerScriptManager().recipeSchemaStorage; + var schemaType = storage.namespace(id.location().getNamespace()).get(id.location().getPath()); + var ops = source.getServer().registryAccess().createSerializationContext(JsonOps.INSTANCE); + + if (schemaType != null) { + var schema = schemaType.schema; + var json = new JsonObject(); + var keys = new JsonArray(); + json.add("keys", keys); + + for (var key : schema.keys) { + keys.add(key.toJson(schemaType, ops)); + } + + if (!schema.uniqueIds().isEmpty()) { + var a = new JsonArray(); + + for (var key : schema.uniqueIds()) { + a.add(key.name); + } + + if (!a.isEmpty()) { + json.add("unique", a); + } + } + + if (!schema.constructorsGenerated()) { + var a = new JsonArray(); + + for (var c : schema.constructors().values()) { + a.add(c.toJson(schemaType, ops)); + } + + if (!a.isEmpty()) { + json.add("constructors", a); + } + } + + ConsoleJS.SERVER.info("JSON of " + id.location() + ": (May be inaccurate!)\n" + JsonUtils.toPrettyString(json)); + } + + return 1; + } } \ No newline at end of file diff --git a/src/main/java/dev/latvian/mods/kubejs/recipe/KubeRecipe.java b/src/main/java/dev/latvian/mods/kubejs/recipe/KubeRecipe.java index 01fbbfe8b..47e209703 100644 --- a/src/main/java/dev/latvian/mods/kubejs/recipe/KubeRecipe.java +++ b/src/main/java/dev/latvian/mods/kubejs/recipe/KubeRecipe.java @@ -399,7 +399,7 @@ public ResourceLocation getType() { public ResourceLocation getOrCreateId() { if (id == null) { var js = getSerializationTypeFunction(); - var ids = CommonProperties.get().ignoreCustomUniqueRecipeIds ? null : js.schemaType.schema.uniqueIdFunction.apply(this); + var ids = CommonProperties.get().ignoreCustomUniqueRecipeIds ? null : js.schemaType.schema.buildUniqueId(this); var prefix = js.id.getNamespace() + ":kjs/"; diff --git a/src/main/java/dev/latvian/mods/kubejs/recipe/RecipeKey.java b/src/main/java/dev/latvian/mods/kubejs/recipe/RecipeKey.java index 8a7423e77..e6cf50bde 100644 --- a/src/main/java/dev/latvian/mods/kubejs/recipe/RecipeKey.java +++ b/src/main/java/dev/latvian/mods/kubejs/recipe/RecipeKey.java @@ -1,10 +1,16 @@ package dev.latvian.mods.kubejs.recipe; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; import com.mojang.serialization.Codec; +import com.mojang.serialization.DynamicOps; import dev.latvian.mods.kubejs.recipe.component.ComponentRole; import dev.latvian.mods.kubejs.recipe.component.RecipeComponent; import dev.latvian.mods.kubejs.recipe.schema.RecipeOptional; import dev.latvian.mods.kubejs.recipe.schema.RecipeSchema; +import dev.latvian.mods.kubejs.recipe.schema.RecipeSchemaType; import dev.latvian.mods.kubejs.util.Cast; import dev.latvian.mods.rhino.type.TypeInfo; @@ -204,4 +210,55 @@ public RecipeKey alwaysWrite() { public String getPreferredBuilderKey() { return functionNames == null ? name : functionNames.getFirst(); } + + public JsonObject toJson(RecipeSchemaType type, DynamicOps ops) { + var json = new JsonObject(); + json.addProperty("name", name); + + if (!role.isOther()) { + json.addProperty("role", role.getSerializedName()); + } + + json.addProperty("type", component.toString()); + + if (optional != null) { + if (optional.isDefault()) { + json.add("optional", JsonNull.INSTANCE); + } else { + json.add("optional", codec.encodeStart(ops, optional.getDefaultValue(type)).getOrThrow()); + } + } + + if (names.size() > 1) { + var a = new JsonArray(); + + for (var n : names) { + if (!n.equals(name)) { + a.add(n); + } + } + + json.add("alternative_names", a); + } + + if (excluded) { + json.addProperty("excluded", true); + } + + if (functionNames != null) { + var a = new JsonArray(); + functionNames.forEach(a::add); + json.add("function_names", a); + } + + if (allowEmpty) { + json.addProperty("allow_empty", true); + } + + if (alwaysWrite) { + json.addProperty("always_write", true); + } + + return json; + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/recipe/component/NumberComponent.java b/src/main/java/dev/latvian/mods/kubejs/recipe/component/NumberComponent.java index 80c2e5395..7f7cf969e 100644 --- a/src/main/java/dev/latvian/mods/kubejs/recipe/component/NumberComponent.java +++ b/src/main/java/dev/latvian/mods/kubejs/recipe/component/NumberComponent.java @@ -128,6 +128,21 @@ default NumberComponent max(T max) { return range(min(), max); } + default String toString(String name, T min, T max) { + var mn = min(); + var mx = max(); + + if (min.equals(mn) && max.equals(mx)) { + return name; + } else if (min.equals(mn)) { + return name + ""; + } else if (max.equals(mx)) { + return name + "<" + mn + ",max>"; + } else { + return name + "<" + mn + "," + mx + ">"; + } + } + record IntRange(Integer min, Integer max, Codec codec) implements NumberComponent { @Override public Codec codec() { @@ -151,7 +166,7 @@ public IntRange range(Integer min, Integer max) { @Override public String toString() { - return "int"; + return toString("int", Integer.MIN_VALUE, Integer.MAX_VALUE); } } @@ -180,7 +195,7 @@ public LongRange range(Long min, Long max) { @Override public String toString() { - return "long"; + return toString("long", Long.MIN_VALUE, Long.MAX_VALUE); } } @@ -207,7 +222,7 @@ public FloatRange range(Float min, Float max) { @Override public String toString() { - return "float"; + return toString("float", Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY); } } @@ -234,7 +249,7 @@ public DoubleRange range(Double min, Double max) { @Override public String toString() { - return "double"; + return toString("double", Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } } } diff --git a/src/main/java/dev/latvian/mods/kubejs/recipe/schema/RecipeConstructor.java b/src/main/java/dev/latvian/mods/kubejs/recipe/schema/RecipeConstructor.java index 582611a61..b5a696e58 100644 --- a/src/main/java/dev/latvian/mods/kubejs/recipe/schema/RecipeConstructor.java +++ b/src/main/java/dev/latvian/mods/kubejs/recipe/schema/RecipeConstructor.java @@ -1,6 +1,9 @@ package dev.latvian.mods.kubejs.recipe.schema; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.mojang.serialization.DynamicOps; import dev.latvian.mods.kubejs.recipe.KubeRecipe; import dev.latvian.mods.kubejs.recipe.RecipeKey; import dev.latvian.mods.kubejs.recipe.RecipeTypeFunction; @@ -72,4 +75,28 @@ public void setValues(Context cx, KubeRecipe recipe, RecipeSchemaType schemaType recipe.setValue(entry.getKey(), Cast.to(entry.getValue().getDefaultValue(schemaType))); } } + + public JsonObject toJson(RecipeSchemaType type, DynamicOps ops) { + var json = new JsonObject(); + + var k = new JsonArray(keys.size()); + + for (var key : keys) { + k.add(key.name); + } + + json.add("keys", k); + + if (!overrides.isEmpty()) { + var o = new JsonObject(); + + for (var entry : overrides.entrySet()) { + o.add(entry.getKey().name, entry.getKey().codec.encodeStart(ops, Cast.to(entry.getValue().getDefaultValue(type))).getOrThrow()); + } + + json.add("overrides", o); + } + + return json; + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/recipe/schema/RecipeSchema.java b/src/main/java/dev/latvian/mods/kubejs/recipe/schema/RecipeSchema.java index c44ac7017..c1fdbd62c 100644 --- a/src/main/java/dev/latvian/mods/kubejs/recipe/schema/RecipeSchema.java +++ b/src/main/java/dev/latvian/mods/kubejs/recipe/schema/RecipeSchema.java @@ -48,7 +48,8 @@ public class RecipeSchema { private int outputCount; private int minRequiredArguments; private Int2ObjectMap constructors; - public Function uniqueIdFunction; + private boolean constructorsGenerated; + private List> uniqueIds; boolean hidden; /** @@ -102,7 +103,7 @@ public RecipeSchema(Map, RecipeOptional> keyOverrides, List... keys) { return constructor(new RecipeConstructor(keys)); } - public RecipeSchema uniqueId(Function uniqueIdFunction) { - this.uniqueIdFunction = uniqueIdFunction; + public RecipeSchema uniqueId(RecipeKey key) { + uniqueIds = List.of(key); return this; } - public RecipeSchema uniqueId(RecipeKey key) { - return uniqueId(r -> { + public RecipeSchema uniqueIds(SequencedCollection> keys) { + uniqueIds = List.copyOf(keys); + return this; + } + + @Nullable + public String buildUniqueId(KubeRecipe r) { + if (uniqueIds.isEmpty()) { + return null; + } else if (uniqueIds.size() == 1) { + var key = uniqueIds.getFirst(); var value = r.getValue(key); if (value != null) { @@ -160,17 +170,7 @@ public RecipeSchema uniqueId(RecipeKey key) { } return null; - }); - } - - public RecipeSchema uniqueIds(SequencedCollection> keys) { - if (keys.isEmpty()) { - return uniqueId(DEFAULT_UNIQUE_ID_FUNCTION); - } else if (keys.size() == 1) { - return uniqueId(keys.getFirst()); - } - - return uniqueId(r -> { + } else { var sb = new StringBuilder(); var builder = new UniqueIdBuilder(new StringBuilder()); boolean first = true; @@ -195,11 +195,12 @@ public RecipeSchema uniqueIds(SequencedCollection> keys) { } return sb.isEmpty() ? null : sb.toString(); - }); + } } public Int2ObjectMap constructors() { if (constructors == null) { + constructorsGenerated = true; constructors = includedKeys.isEmpty() ? new Int2ObjectArrayMap<>() : new Int2ObjectArrayMap<>(includedKeys.size() - minRequiredArguments + 1); boolean dev = DevProperties.get().logRecipeDebug; @@ -220,6 +221,10 @@ public Int2ObjectMap constructors() { return constructors; } + public List> uniqueIds() { + return uniqueIds; + } + public int minRequiredArguments() { return minRequiredArguments; } @@ -236,6 +241,11 @@ public boolean isHidden() { return hidden; } + public boolean constructorsGenerated() { + constructors(); + return constructorsGenerated; + } + public KubeRecipe deserialize(SourceLine sourceLine, RecipeTypeFunction type, @Nullable ResourceLocation id, JsonObject json) { var r = recipeFactory.create(type, sourceLine); r.id = id; diff --git a/src/main/resources/data/minecraft/kubejs/recipe_schema/cooking.json b/src/main/resources/data/minecraft/kubejs/recipe_schema/cooking.json index 554ad378a..82b516aed 100644 --- a/src/main/resources/data/minecraft/kubejs/recipe_schema/cooking.json +++ b/src/main/resources/data/minecraft/kubejs/recipe_schema/cooking.json @@ -13,7 +13,7 @@ { "name": "experience", "role": "output", - "type": "float", + "type": "float<0.0,max>", "function_names": ["xp", "experience"], "optional": 0.0 }, @@ -26,7 +26,6 @@ }, { "name": "category", - "role": "other", "type": "cooking_book_category", "optional": "misc" }