Skip to content

Commit f49b1ec

Browse files
RecipeSchemaProvider (#1075)
* Provided * Woah, information * Whoops * & Another
1 parent 8f7d001 commit f49b1ec

File tree

5 files changed

+408
-0
lines changed

5 files changed

+408
-0
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,34 @@ KubeJS offers native Java type access in script files, meaning that basic Java t
9595
-mymod.internal.HttpUtil // This will *explicitly deny* your class from being used in KubeJS
9696
```
9797

98+
### Recipe Schemas
99+
100+
In addition to registering recipe schemas through your plugin, they can be made via a json file in the `kubejs/recipe_schema` registry. Json schemas are the preferred method of declaring schemas and can be [datagenned](https://docs.neoforged.net/docs/1.21.1/resources/#data-generation) with the provided `RecipeSchemaProvider`!
101+
102+
**Important!** In order to use this data provider, you *must* add kubejs as an existing mod with the `'--exisiting-mod', 'kubejs'` arguments. See [NeoForge's docs](https://docs.neoforged.net/docs/1.21.1/resources/#command-line-arguments) for how to do that.
103+
104+
After that, using the provider is just like any other
105+
106+
```java
107+
@EventBusSubscriber(modid = "mod")
108+
public class DataGen {
109+
110+
@SubscribeEvent
111+
public static void gatherData(GatherDataEvent event) {
112+
event.addProvider(new RecipeSchemaProvider("Mod Recipe Schemas", event) {
113+
@Override
114+
public void add(HolderLookup.Proivder lookup) {
115+
add(ResourceLocation.fromNamespaceAndPath("mod", "recipe"), builder -> {
116+
builder.hidden();
117+
builder.mappings("modRecipe", "hungry");
118+
// And so on, the javadocs of the methods give a brief description of what their data is used for
119+
});
120+
}
121+
});
122+
}
123+
}
124+
```
125+
98126
## Contributing to KubeJS
99127

100128
### Getting Started

src/main/java/dev/latvian/mods/kubejs/recipe/RecipeKey.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,14 @@ public RecipeKey<T> functionNames(List<String> names) {
190190
return this;
191191
}
192192

193+
/**
194+
* Sets a list of names that are used to auto-generate builder functions in JS, e.g. <code>.xp(value)</code>.
195+
* The first one will be the preferred one that ProbeJS and other third-party documentation should recommend.
196+
*/
197+
public RecipeKey<T> functionNames(String... names) {
198+
return functionNames(List.of(name));
199+
}
200+
193201
/**
194202
* Set this in order to always write optional keys, even if their value hasn't changed.
195203
* <p>
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
package dev.latvian.mods.kubejs.recipe;
2+
3+
import com.google.common.collect.ImmutableMap;
4+
import com.google.gson.JsonElement;
5+
import com.mojang.serialization.Codec;
6+
import dev.latvian.mods.kubejs.recipe.schema.RecipeFactoryRegistry;
7+
import dev.latvian.mods.kubejs.recipe.schema.RecipeOptional;
8+
import dev.latvian.mods.kubejs.recipe.schema.RecipeSchemaData;
9+
import dev.latvian.mods.kubejs.recipe.schema.function.RecipeSchemaFunction;
10+
import dev.latvian.mods.kubejs.recipe.schema.postprocessing.RecipePostProcessor;
11+
import dev.latvian.mods.kubejs.server.ServerScriptManager;
12+
import dev.latvian.mods.kubejs.util.Cast;
13+
import dev.latvian.mods.kubejs.util.RegistryAccessContainer;
14+
import net.minecraft.Util;
15+
import net.minecraft.core.HolderLookup;
16+
import net.minecraft.data.CachedOutput;
17+
import net.minecraft.data.DataProvider;
18+
import net.minecraft.data.PackOutput;
19+
import net.minecraft.resources.ResourceLocation;
20+
import net.minecraft.server.packs.PackType;
21+
import net.neoforged.neoforge.data.event.GatherDataEvent;
22+
23+
import java.util.*;
24+
import java.util.concurrent.CompletableFuture;
25+
import java.util.function.Consumer;
26+
27+
/**
28+
* A base provider for generating recipe schemas.
29+
* <p>
30+
* <strong>Important!</strong> KubeJS must be added as an existing mod via
31+
* {@code programArguments.addAll('--existing-mod', 'kubejs')} in your data gen runs block for this to work!
32+
*/
33+
public abstract class RecipeSchemaProvider implements DataProvider {
34+
35+
private final CompletableFuture<HolderLookup.Provider> lookupProvider;
36+
private final String name;
37+
private final RegistryAccessContainer registryAccessContainer;
38+
private final PackOutput.PathProvider path;
39+
private final ImmutableMap.Builder<ResourceLocation, RecipeSchemaData> map;
40+
private final ServerScriptManager scriptManager;
41+
private final RecipeTypeRegistryContext regCtx;
42+
private final Codec<RecipeSchemaData> codec;
43+
44+
public RecipeSchemaProvider(String name, GatherDataEvent event) {
45+
this(name, event, RegistryAccessContainer.BUILTIN);
46+
}
47+
48+
public RecipeSchemaProvider(String name, GatherDataEvent event, RegistryAccessContainer registryAccessContainer) {
49+
this.lookupProvider = event.getLookupProvider();
50+
this.name = name;
51+
this.registryAccessContainer = registryAccessContainer;
52+
path = event.getGenerator().getPackOutput().createPathProvider(PackOutput.Target.DATA_PACK, "kubejs/recipe_schema");
53+
map = ImmutableMap.builder();
54+
scriptManager = ServerScriptManager.createForDataGen();
55+
regCtx = new RecipeTypeRegistryContext(
56+
registryAccessContainer,
57+
scriptManager.recipeSchemaStorage
58+
);
59+
scriptManager.recipeSchemaStorage.fireEvents(registryAccessContainer, event.getResourceManager(PackType.SERVER_DATA));
60+
codec = RecipeSchemaData.CODEC.apply(regCtx);
61+
}
62+
63+
public final RegistryAccessContainer registryAccessContainer() {
64+
return registryAccessContainer;
65+
}
66+
67+
public final ServerScriptManager serverScriptManager() {
68+
return scriptManager;
69+
}
70+
71+
public final RecipeTypeRegistryContext recipeTypeRegistryContext() {
72+
return regCtx;
73+
}
74+
75+
public abstract void add(HolderLookup.Provider lookup);
76+
77+
public void add(ResourceLocation id, RecipeSchemaData schema) {
78+
map.put(id, schema);
79+
}
80+
81+
public void add(ResourceLocation id, Consumer<SchemaDataBuilder> builder) {
82+
add(id, Util.make(new SchemaDataBuilder(), builder).build());
83+
}
84+
85+
public void onlyKeys(ResourceLocation id, RecipeKey<?>... keys) {
86+
add(id, b -> b.keys(keys));
87+
}
88+
89+
public RecipeSchemaData.RecipeKeyData keyData(RecipeKey<?> key) {
90+
if (key.functionNames == null) {
91+
key.noFunctions();
92+
}
93+
return new RecipeSchemaData.RecipeKeyData(
94+
key.name,
95+
key.role,
96+
key.component,
97+
Optional.ofNullable(key.optional)
98+
.map(o -> o.isDefault() ?
99+
null :
100+
key.codec.encodeStart(
101+
registryAccessContainer.json(),
102+
Cast.to(o.getValueForDataGeneration())
103+
).getOrThrow()),
104+
key.optional == RecipeOptional.DEFAULT,
105+
new ArrayList<>(key.names),
106+
key.excluded,
107+
key.functionNames,
108+
key.alwaysWrite
109+
);
110+
}
111+
112+
@Override
113+
public CompletableFuture<?> run(CachedOutput output) {
114+
return lookupProvider.thenCompose(p -> {
115+
add(p);
116+
return CompletableFuture.allOf(
117+
map.buildOrThrow().entrySet().stream()
118+
.map(e -> DataProvider.saveStable(output, p, codec, e.getValue(), path.json(e.getKey())))
119+
.toArray(CompletableFuture[]::new)
120+
);
121+
});
122+
}
123+
124+
@Override
125+
public String getName() {
126+
return name;
127+
}
128+
129+
public class SchemaDataBuilder {
130+
131+
private ResourceLocation parent, overrideType, recipeFactory;
132+
private List<RecipeSchemaData.RecipeKeyData> keys;
133+
private List<RecipeSchemaData.ConstructorData> constructors;
134+
private Map<String, RecipeSchemaFunction> functions;
135+
private final Map<String, JsonElement> overrideKeys = new HashMap<>();
136+
boolean hidden = false;
137+
private final List<String> mappings = new ArrayList<>();
138+
private List<String> unique;
139+
private List<RecipePostProcessor> postProcessors;
140+
private RecipeSchemaData.MergeData mergeData = RecipeSchemaData.MergeData.DEFAULT;
141+
142+
/**
143+
* Sets the parent recipe type, which acts as a fallback/proxy for recipe methods. See vanilla's smoking &
144+
* blasting recipe types for examples.
145+
*/
146+
public SchemaDataBuilder parent(ResourceLocation parent) {
147+
this.parent = parent;
148+
return this;
149+
}
150+
151+
/**
152+
* Specifies an alternative recipe serializer to use instead of the one associated with this recipe
153+
*/
154+
public SchemaDataBuilder overrideType(ResourceLocation type) {
155+
overrideType = type;
156+
return this;
157+
}
158+
159+
/**
160+
* Set the {@link dev.latvian.mods.kubejs.recipe.schema.KubeRecipeFactory recipe factory} to be used for this recipe
161+
* type. See {@link dev.latvian.mods.kubejs.plugin.KubeJSPlugin#registerRecipeFactories(RecipeFactoryRegistry) #registerRecipeFactories}
162+
* for registering custom factories
163+
*/
164+
public SchemaDataBuilder recipeFactory(ResourceLocation factory) {
165+
recipeFactory = factory;
166+
return this;
167+
}
168+
169+
/**
170+
* Adds the {@code RecipeKey}s, automatically converting them to {@code RecipeKeyData}s
171+
*/
172+
public SchemaDataBuilder keys(RecipeKey<?>... keys) {
173+
return keys(List.of(keys));
174+
}
175+
176+
/**
177+
* Adds the {@code RecipeKey}s, automatically converting them to {@code RecipeKeyData}s
178+
*/
179+
public SchemaDataBuilder keys(List<RecipeKey<?>> keys) {
180+
return keyDatas(keys.stream().map(RecipeSchemaProvider.this::keyData).toList());
181+
}
182+
183+
/**
184+
* Adds the raw {@code RecipeKeyData}s
185+
*/
186+
public SchemaDataBuilder keyDatas(RecipeSchemaData.RecipeKeyData... keys) {
187+
return keyDatas(List.of(keys));
188+
}
189+
190+
/**
191+
* Adds the raw {@code RecipeKeyData}s
192+
*/
193+
public SchemaDataBuilder keyDatas(List<RecipeSchemaData.RecipeKeyData> keys) {
194+
if (this.keys == null) {
195+
this.keys = new ArrayList<>(keys);
196+
} else {
197+
this.keys.addAll(keys);
198+
}
199+
return this;
200+
}
201+
202+
/**
203+
* Add custom constructors which can be used instead of the key-based default
204+
*/
205+
public SchemaDataBuilder constructors(RecipeSchemaData.ConstructorData... constructors) {
206+
return constructors(List.of(constructors));
207+
}
208+
209+
/**
210+
* Add custom constructors which can be used instead of the key-based default
211+
*/
212+
public SchemaDataBuilder constructors(List<RecipeSchemaData.ConstructorData> constructors) {
213+
if (this.constructors == null) {
214+
this.constructors = new ArrayList<>(constructors);
215+
} else {
216+
this.constructors.addAll(constructors);
217+
}
218+
return this;
219+
}
220+
221+
/**
222+
* Add the function to the recipe, used like {@code event.recipes.my.recipe(<recipe args>).<name>(<function args>)}
223+
*/
224+
public SchemaDataBuilder function(String name, RecipeSchemaFunction function) {
225+
return functions(Map.of(name, function));
226+
}
227+
228+
/**
229+
* Add the functions to the recipe, see {@link #function(String, RecipeSchemaFunction)}
230+
*/
231+
public SchemaDataBuilder functions(Map<String, RecipeSchemaFunction> functions) {
232+
if (this.functions == null) {
233+
this.functions = new HashMap<>(functions);
234+
} else {
235+
this.functions.putAll(functions);
236+
}
237+
return this;
238+
}
239+
240+
/**
241+
* Specify the optional value of a key
242+
*/
243+
public SchemaDataBuilder overrideKey(String key, JsonElement optionalValue) {
244+
overrideKeys.put(key, optionalValue);
245+
return this;
246+
}
247+
248+
/**
249+
* Specify specific overrides for the optional values of keys
250+
*/
251+
public SchemaDataBuilder overrideKeys(Map<String, JsonElement> overrideKeys) {
252+
this.overrideKeys.putAll(overrideKeys);
253+
return this;
254+
}
255+
256+
/**
257+
* Nominally, if this recipe type should be hidden... but this value appears to be unused
258+
*/
259+
public SchemaDataBuilder hidden() {
260+
return hidden(true);
261+
}
262+
263+
/**
264+
* Nominally, if this recipe type should be hidden... but this value appears to be unused
265+
*/
266+
public SchemaDataBuilder hidden(boolean hidden) {
267+
this.hidden = hidden;
268+
return this;
269+
}
270+
271+
/**
272+
* The names for accessing this recipe via a special method, e.g. {@code event.recipes.myMapping(...}
273+
*/
274+
public SchemaDataBuilder mappings(String... mappings) {
275+
return mappings(List.of(mappings));
276+
}
277+
278+
/**
279+
* The names for accessing this recipe via a special method, e.g. {@code event.recipes.myMapping(...}
280+
*/
281+
public SchemaDataBuilder mappings(List<String> mappings) {
282+
this.mappings.addAll(mappings);
283+
return this;
284+
}
285+
286+
/**
287+
* The keys to use when generating a unique id for the recipe
288+
*/
289+
public SchemaDataBuilder keysForUniqueId(String... keys) {
290+
return keysForUniqueId(List.of(keys));
291+
}
292+
293+
/**
294+
* The keys to use when generating a unique id for the recipe
295+
*/
296+
public SchemaDataBuilder keysForUniqueId(List<String> keys) {
297+
if (unique == null) {
298+
unique = new ArrayList<>(keys);
299+
} else {
300+
unique.addAll(keys);
301+
}
302+
return this;
303+
}
304+
305+
/**
306+
* Post processors to apply to recipes, see {@link dev.latvian.mods.kubejs.recipe.schema.postprocessing.KeyPatternCleanupPostProcessor KeyPatternCleanupPostProcessor}
307+
*/
308+
public SchemaDataBuilder postProcessors(RecipePostProcessor... processors) {
309+
return postProcessors(List.of(processors));
310+
}
311+
312+
/**
313+
* Post processors to apply to recipes, see {@link dev.latvian.mods.kubejs.recipe.schema.postprocessing.KeyPatternCleanupPostProcessor KeyPatternCleanupPostProcessor}
314+
*/
315+
public SchemaDataBuilder postProcessors(List<RecipePostProcessor> processors) {
316+
if (postProcessors == null) {
317+
postProcessors = new ArrayList<>(processors);
318+
} else {
319+
postProcessors.addAll(processors);
320+
}
321+
return this;
322+
}
323+
324+
/**
325+
* If values from {@link #parent(ResourceLocation) parent schemas} should be merged when baking the recipe schema
326+
*/
327+
public SchemaDataBuilder mergeData(boolean keys, boolean constructors, boolean unique, boolean postProcessors) {
328+
mergeData = new RecipeSchemaData.MergeData(keys, constructors, unique, postProcessors);
329+
return this;
330+
}
331+
332+
RecipeSchemaData build() {
333+
return new RecipeSchemaData(
334+
Optional.ofNullable(parent),
335+
Optional.ofNullable(overrideType),
336+
Optional.ofNullable(recipeFactory),
337+
Optional.ofNullable(keys),
338+
Optional.ofNullable(constructors),
339+
Optional.ofNullable(functions),
340+
overrideKeys,
341+
Optional.of(hidden),
342+
mappings,
343+
Optional.ofNullable(unique),
344+
Optional.ofNullable(postProcessors),
345+
mergeData
346+
);
347+
}
348+
}
349+
}

0 commit comments

Comments
 (0)