Skip to content

Commit a4c167d

Browse files
committed
feat: added custom unifier dir and fluid unification support
* No default unification for fluids (we don't add custom fluids) * Unifier & rule entries must be added manually for fluid recipes * Added new replacements in rules: `<fluid_map>` and `<fluid_tag_map>` * `<tag_map>` replacement in rules is now `<item_tag_map>` * `<tag_map>` is still accepted for now as an alias
1 parent 5644a62 commit a4c167d

4 files changed

Lines changed: 151 additions & 61 deletions

File tree

src/main/java/dev/ftb/mods/ftbmaterials/unification/DefaultCustomRules.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ public static void create(Path path) {
1414
RecipeTweaker tweaker = RecipeTweaker.createNew();
1515

1616
tweaker.addRule(ResourceLocation.fromNamespaceAndPath("immersiveengineering", "arcfurnace"),
17-
new Rule("input/tag", RewriteAction.create("item", "<tag_map>")),
18-
new Rule("additives/tag", RewriteAction.create("item", "<tag_map>")),
17+
new Rule("input/tag", RewriteAction.create("item", "<item_tag_map>")),
18+
new Rule("additives/tag", RewriteAction.create("item", "<item_tag_map>")),
1919
new Rule("input/item", RewriteAction.create("item", "<item_map>")),
2020
new Rule("additives/item", RewriteAction.create("item", "<item_map>"))
2121
);
2222
tweaker.addRule(ResourceLocation.fromNamespaceAndPath("immersiveengineering", "alloy"),
23-
new Rule("input0/tag", RewriteAction.create("item", "<tag_map>")),
24-
new Rule("input0/tag", RewriteAction.create("item", "<tag_map>")),
23+
new Rule("input0/tag", RewriteAction.create("item", "<item_tag_map>")),
24+
new Rule("input0/tag", RewriteAction.create("item", "<item_tag_map>")),
2525
new Rule("input1/item", RewriteAction.create("item", "<item_map>")),
2626
new Rule("input1/item", RewriteAction.create("item", "<item_map>"))
2727
);
@@ -43,7 +43,7 @@ public static void create(Path path) {
4343
private static void standardIERule(RecipeTweaker tweaker, String type) {
4444
// for machines with just a single input
4545
tweaker.addRule(ResourceLocation.fromNamespaceAndPath("immersiveengineering", type),
46-
new Rule("input/tag", RewriteAction.create("item", "<tag_map>")),
46+
new Rule("input/tag", RewriteAction.create("item", "<item_tag_map>")),
4747
new Rule("input/item", RewriteAction.create("item", "<item_map>"))
4848
);
4949
}

src/main/java/dev/ftb/mods/ftbmaterials/unification/RecipeTweaker.java

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.nio.file.Files;
1818
import java.nio.file.Path;
1919
import java.util.*;
20+
import java.util.function.BiFunction;
2021
import java.util.function.Function;
2122
import java.util.stream.Stream;
2223

@@ -46,31 +47,17 @@ public static RecipeTweaker load(Path path) throws IOException {
4647
if (Files.exists(path)) {
4748
JsonElement json = JsonParser.parseString(Files.readString(path));
4849
RecipeTweaker res = CODEC.parse(JsonOps.INSTANCE, json).getOrThrow();
49-
res.scanExtraRulesDir();
50+
UnifierManager.loadExtraJsonFiles(UnifierManager.RULES_DIR, el ->
51+
res.addExtraRules(RULES_CODEC.parse(JsonOps.INSTANCE, el).getOrThrow()));
5052
return res;
5153
} else {
5254
return EMPTY;
5355
}
5456
}
5557

56-
private void scanExtraRulesDir() {
57-
try (Stream<Path> s = Files.list(UnifierManager.RULES_DIR)) {
58-
s.filter(p -> p.toString().endsWith(".json")).forEach(rulesFile -> {
59-
try {
60-
JsonElement rulesJson = JsonParser.parseString(Files.readString(rulesFile));
61-
addExtraRules(RULES_CODEC.parse(JsonOps.INSTANCE, rulesJson).getOrThrow());
62-
} catch (Exception ex) {
63-
FTBMaterials.LOGGER.error("can't read rules file {}: {}", rulesFile, ex.getMessage());
64-
}
65-
});
66-
} catch (Exception ex) {
67-
FTBMaterials.LOGGER.error("can't read directory {}: {}", UnifierManager.RULES_DIR, ex.getMessage());
68-
}
69-
}
70-
7158
private void addExtraRules(Map<String, List<Rule>> ruleMap) {
7259
ruleMap.forEach((type, rules) ->
73-
ruleDB.computeIfAbsent(type, ignored -> new ArrayList<>()).addAll(rules)
60+
ruleDB.merge(type, rules, (r1, r2) -> Stream.concat(r1.stream(), r2.stream()).toList())
7461
);
7562
}
7663

@@ -157,6 +144,53 @@ public void addRule(ResourceLocation recipeType, Rule... rules) {
157144
ruleDB.computeIfAbsent(recipeType.toString(), ignored -> new ArrayList<>()).addAll(List.of(rules));
158145
}
159146

147+
private enum MappingType {
148+
NONE(
149+
(db, in) -> new Mappings(in, in),
150+
(in, mapping) -> in
151+
),
152+
ITEM(
153+
(db, in) -> new Mappings(db.lookupItem(in).orElse(in), db.lookupItemTag(in).orElse(in)),
154+
(in, mappings) -> in.replace("<item_map>", mappings.objMapping).replace("<item_tag_map>", mappings.tagMapping)
155+
),
156+
FLUID(
157+
(db, in) -> new Mappings(db.lookupFluid(in).orElse(in), db.lookupFluidTag(in).orElse(in)),
158+
(in, mappings) -> in.replace("<fluid_map>", mappings.objMapping).replace("<fluid_tag_map>", mappings.tagMapping)
159+
);
160+
161+
private final BiFunction<UnifierDB, String, Mappings> mappingFactory;
162+
private final BiFunction<String, Mappings, String> replacer;
163+
164+
MappingType(BiFunction<UnifierDB, String, Mappings> mappingFactory, BiFunction<String, Mappings, String> replacer) {
165+
this.mappingFactory = mappingFactory;
166+
this.replacer = replacer;
167+
}
168+
169+
public static MappingType fromReplacementString(String str) {
170+
if (str.contains("<item_")) {
171+
return ITEM;
172+
} else if (str.contains("<fluid_")) {
173+
return FLUID;
174+
} else {
175+
return NONE;
176+
}
177+
}
178+
179+
public Mappings createMappings(UnifierDB db, String input) {
180+
return mappingFactory.apply(db, input);
181+
}
182+
183+
String doReplacement(String input, Mappings mappings) {
184+
return replacer.apply(input, mappings);
185+
}
186+
}
187+
188+
private record Mappings(String objMapping, String tagMapping) {
189+
boolean differsFromInput(String input) {
190+
return !objMapping.equals(input) || !tagMapping.equals(input);
191+
}
192+
}
193+
160194
public record Rule(String path, RewriteAction action) {
161195
public static final Codec<Rule> CODEC = RecordCodecBuilder.create(builder -> builder.group(
162196
Codec.STRING.fieldOf("path").forGetter(Rule::path),
@@ -173,14 +207,12 @@ public boolean apply(JsonObject recipeJson, UnifierDB unifierDB) {
173207
String fieldName = pair.getSecond();
174208

175209
String curVal = json.get(fieldName).getAsString();
176-
String tagMapped = unifierDB.lookupItemTag(curVal).orElse(curVal);
177-
String itemMapped = unifierDB.lookupItem(curVal).orElse(curVal);
178-
179-
if (!action.inputValue.isEmpty() && action.inputValue.equals(curVal)
180-
|| action.inputValue.isEmpty() && (!tagMapped.equals(curVal) || !itemMapped.equals(curVal))) {
181-
String newVal = action.outputValue
182-
.replace("<tag_map>", tagMapped)
183-
.replace("<item_map>", itemMapped);
210+
var mappingType = MappingType.fromReplacementString(action.outputValue);
211+
var mappings = mappingType.createMappings(unifierDB, curVal);
212+
213+
if (!action.inputFilter.isEmpty() && action.inputFilter.equals(curVal)
214+
|| action.inputFilter.isEmpty() && mappings.differsFromInput(curVal)) {
215+
String newVal = mappingType.doReplacement(action.outputValue, mappings);
184216
json.addProperty(action.fieldName, newVal);
185217
if (!fieldName.equals(action.fieldName)) {
186218
json.remove(fieldName);
@@ -242,13 +274,19 @@ private void collectNodes(JsonElement el, String part0, String[] otherParts, Lis
242274
}
243275
}
244276

245-
public record RewriteAction(String fieldName, String outputValue, String inputValue) {
277+
public record RewriteAction(String fieldName, String outputValue, String inputFilter) {
246278
public static final Codec<RewriteAction> CODEC = RecordCodecBuilder.create(builder -> builder.group(
247279
Codec.STRING.fieldOf("field").forGetter(RewriteAction::fieldName),
248280
Codec.STRING.fieldOf("output_value").forGetter(RewriteAction::outputValue),
249-
Codec.STRING.optionalFieldOf("input_value","").forGetter(RewriteAction::inputValue)
281+
Codec.STRING.optionalFieldOf("input_value","").forGetter(RewriteAction::inputFilter)
250282
).apply(builder, RewriteAction::new));
251283

284+
public RewriteAction(String fieldName, String outputValue, String inputFilter) {
285+
this.fieldName = fieldName;
286+
this.outputValue = outputValue.replace("<tag_map>", "<item_tag_map>"); // legacy
287+
this.inputFilter = inputFilter;
288+
}
289+
252290
public static RewriteAction create(String fieldName, String outputValue) {
253291
return new RewriteAction(fieldName, outputValue, "");
254292
}

src/main/java/dev/ftb/mods/ftbmaterials/unification/UnifierDB.java

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
import dev.ftb.mods.ftbmaterials.resources.ResourceType;
1515
import dev.ftb.mods.ftbmaterials.util.CachedTagKeyLookup;
1616
import net.minecraft.core.Holder;
17+
import net.minecraft.core.Registry;
1718
import net.minecraft.core.registries.BuiltInRegistries;
1819
import net.minecraft.core.registries.Registries;
1920
import net.minecraft.resources.ResourceLocation;
2021
import net.minecraft.tags.TagKey;
2122
import net.minecraft.world.item.Item;
2223
import net.minecraft.world.level.block.Block;
2324
import net.minecraft.world.level.block.state.BlockState;
25+
import net.minecraft.world.level.material.Fluid;
2426
import net.neoforged.neoforge.common.util.Lazy;
2527

2628
import java.io.IOException;
@@ -32,38 +34,55 @@
3234
import java.util.stream.Collectors;
3335

3436
public class UnifierDB {
37+
private static final Codec<Map<String, String>> MUTABLE_STRING_MAP = Codec.unboundedMap(Codec.STRING, Codec.STRING)
38+
.xmap((Function<Map<String, String>, Map<String, String>>) ConcurrentHashMap::new, Function.identity());
39+
3540
public static final Codec<UnifierDB> CODEC = RecordCodecBuilder.create(builder -> builder.group(
36-
Codec.unboundedMap(Codec.STRING, Codec.STRING)
37-
.fieldOf("items").forGetter(db -> db.itemMap),
38-
Codec.unboundedMap(Codec.STRING, Codec.STRING)
39-
.fieldOf("item_tags").forGetter(db -> db.itemTagMap),
40-
Codec.unboundedMap(Codec.STRING, Codec.STRING)
41-
.xmap((Function<Map<String, String>, Map<String, String>>) ConcurrentHashMap::new, Map::copyOf)
42-
.fieldOf("ore_blocks").forGetter(db -> db.blockMap)
41+
MUTABLE_STRING_MAP.optionalFieldOf("items", new ConcurrentHashMap<>()).forGetter(db -> db.itemMap),
42+
MUTABLE_STRING_MAP.optionalFieldOf("item_tags", new ConcurrentHashMap<>()).forGetter(db -> db.itemTagMap),
43+
MUTABLE_STRING_MAP.optionalFieldOf("fluids", new ConcurrentHashMap<>()).forGetter(db -> db.fluidMap),
44+
MUTABLE_STRING_MAP.optionalFieldOf("fluid_tags", new ConcurrentHashMap<>()).forGetter(db -> db.fluidTagMap),
45+
MUTABLE_STRING_MAP.optionalFieldOf("ore_blocks", new ConcurrentHashMap<>()).forGetter(db -> db.blockMap)
4346
).apply(builder, UnifierDB::new));
4447

45-
public static final UnifierDB EMPTY = new UnifierDB(Map.of(), Map.of(), Map.of());
48+
public static final UnifierDB EMPTY = new UnifierDB(Map.of(), Map.of(), Map.of(), Map.of(), Map.of());
4649

4750
private final Map<String,String> itemMap;
4851
private final Map<String,String> itemTagMap;
49-
private final Lazy<Map<Item,Item>> itemByItemMap = Lazy.of(this::buildItemByItemMap);
50-
52+
private final Map<String,String> fluidMap;
53+
private final Map<String,String> fluidTagMap;
5154
private final Map<String,String> blockMap;
55+
56+
private final Lazy<Map<Item,Item>> itemByItemMap = Lazy.of(this::buildItemByItemMap);
57+
private final Lazy<Map<Fluid,Fluid>> fluidByFluidMap = Lazy.of(this::buildFluidByFluidMap);
5258
private final Lazy<Map<Block,Block>> blockByBlockMap = Lazy.of(this::buildBlockByBlockMap);
5359

5460
private UnifierDB() {
55-
this(new HashMap<>(), new HashMap<>(), new HashMap<>());
61+
this(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
5662
}
5763

58-
private UnifierDB(Map<String, String> itemMap, Map<String, String> itemTagMap, Map<String, String> blockMap) {
64+
private UnifierDB(Map<String, String> itemMap, Map<String, String> itemTagMap, Map<String, String> fluidMap, Map<String, String> fluidTagMap, Map<String, String> blockMap) {
5965
this.itemMap = itemMap;
6066
this.itemTagMap = itemTagMap;
67+
this.fluidMap = fluidMap;
68+
this.fluidTagMap = fluidTagMap;
6169
this.blockMap = blockMap;
6270
}
6371

6472
public static UnifierDB load(Path path) throws IOException {
6573
JsonElement json = JsonParser.parseString(Files.readString(path));
66-
return CODEC.parse(JsonOps.INSTANCE, json).getOrThrow();
74+
UnifierDB unifierDB = CODEC.parse(JsonOps.INSTANCE, json).getOrThrow();
75+
UnifierManager.loadExtraJsonFiles(UnifierManager.UNIFIER_DIR, el ->
76+
unifierDB.addExtraUnifierEntries(CODEC.parse(JsonOps.INSTANCE, el).getOrThrow()));
77+
return unifierDB;
78+
}
79+
80+
private void addExtraUnifierEntries(UnifierDB extra) {
81+
itemMap.putAll(extra.itemMap);
82+
itemTagMap.putAll(extra.itemTagMap);
83+
fluidMap.putAll(extra.fluidMap);
84+
fluidTagMap.putAll(extra.fluidTagMap);
85+
blockMap.putAll(extra.blockMap);
6786
}
6887

6988
public static UnifierDB build() {
@@ -94,6 +113,18 @@ public Optional<String> lookupItemTag(String key) {
94113
return Optional.ofNullable(itemTagMap.get(key));
95114
}
96115

116+
public Optional<String> lookupFluid(String key) {
117+
return Optional.ofNullable(fluidMap.get(key));
118+
}
119+
120+
public Optional<Fluid> lookupFluid(Fluid fluid) {
121+
return Optional.ofNullable(fluidByFluidMap.get().get(fluid));
122+
}
123+
124+
public Optional<String> lookupFluidTag(String key) {
125+
return Optional.ofNullable(fluidTagMap.get(key));
126+
}
127+
97128
public Optional<Block> lookupBlock(Block block) {
98129
return Optional.ofNullable(blockByBlockMap.get().get(block));
99130
}
@@ -125,7 +156,7 @@ private void buildItemTags(Set<TagKey<Item>> itemTags) {
125156
Item item = ftbMaterialsItem == null ? vanillaFallbackItem : ftbMaterialsItem;
126157
if (item != null) {
127158
otherItems.forEach(other -> addItemMapping(other, item));
128-
addTagMapping(tag, item);
159+
addItemTagMapping(tag, item);
129160
}
130161
}
131162
// special cases for silicon & sawdust
@@ -135,7 +166,7 @@ private void buildItemTags(Set<TagKey<Item>> itemTags) {
135166

136167
private void specialCase(Resource resource, ResourceType resourceType, TagKey<Item> tag) {
137168
ResourceRegistries.get(resource).getItemFromType(resourceType).ifPresent(itemHolder ->
138-
addTagMapping(tag, itemHolder.get()));
169+
addItemTagMapping(tag, itemHolder.get()));
139170

140171
BuiltInRegistries.ITEM.getTag(tag).ifPresent(items -> {
141172
Item[] ftbItem = new Item[] { null };
@@ -189,7 +220,7 @@ public void addItemMapping(Item from, Item to) {
189220
itemMap.put(BuiltInRegistries.ITEM.getKey(from).toString(), BuiltInRegistries.ITEM.getKey(to).toString());
190221
}
191222

192-
public void addTagMapping(TagKey<Item> from, Item to) {
223+
public void addItemTagMapping(TagKey<Item> from, Item to) {
193224
itemTagMap.put(from.location().toString(), BuiltInRegistries.ITEM.getKey(to).toString());
194225
}
195226

@@ -226,22 +257,23 @@ private static <T> Set<TagKey<T>> collectTags(Resource type, ResourceType resour
226257
}
227258

228259
private Map<Item, Item> buildItemByItemMap() {
229-
Map<Item, Item> res = new HashMap<>();
230-
itemMap.forEach((in, out) ->
231-
BuiltInRegistries.ITEM.getOptional(ResourceLocation.parse(in)).ifPresent(itemIn ->
232-
BuiltInRegistries.ITEM.getOptional(ResourceLocation.parse(out)).ifPresent(itemOut ->
233-
res.put(itemIn, itemOut)
234-
)
235-
));
236-
return res;
260+
return buildXbyXMap(itemMap, BuiltInRegistries.ITEM);
261+
}
262+
263+
private Map<Fluid, Fluid> buildFluidByFluidMap() {
264+
return buildXbyXMap(fluidMap, BuiltInRegistries.FLUID);
237265
}
238266

239267
private Map<Block, Block> buildBlockByBlockMap() {
240-
Map<Block, Block> res = new ConcurrentHashMap<>();
241-
blockMap.forEach((in, out) ->
242-
BuiltInRegistries.BLOCK.getOptional(ResourceLocation.parse(in)).ifPresent(blockIn ->
243-
BuiltInRegistries.BLOCK.getOptional(ResourceLocation.parse(out)).ifPresent(blockOut ->
244-
res.put(blockIn, blockOut)
268+
return buildXbyXMap(blockMap, BuiltInRegistries.BLOCK);
269+
}
270+
271+
private static <T> Map<T, T> buildXbyXMap(Map<String,String> unifierMap, Registry<T> registry) {
272+
Map<T, T> res = new HashMap<>();
273+
unifierMap.forEach((in, out) ->
274+
registry.getOptional(ResourceLocation.parse(in)).ifPresent(objIn ->
275+
registry.getOptional(ResourceLocation.parse(out)).ifPresent(objOut ->
276+
res.put(objIn, objOut)
245277
)
246278
));
247279
return res;

src/main/java/dev/ftb/mods/ftbmaterials/unification/UnifierManager.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
package dev.ftb.mods.ftbmaterials.unification;
22

33
import com.google.gson.JsonElement;
4+
import com.google.gson.JsonParser;
45
import dev.ftb.mods.ftbmaterials.FTBMaterials;
56
import net.neoforged.fml.loading.FMLPaths;
67

78
import java.io.IOException;
89
import java.nio.file.Files;
910
import java.nio.file.Path;
11+
import java.util.function.Consumer;
12+
import java.util.stream.Stream;
1013

1114
public enum UnifierManager {
1215
INSTANCE;
1316

1417
private static final Path CONFIG_DIR = FMLPaths.GAMEDIR.get().resolve("config").resolve(FTBMaterials.MOD_ID);
1518

1619
public static final Path RULES_DIR = CONFIG_DIR.resolve("rules");
20+
public static final Path UNIFIER_DIR = CONFIG_DIR.resolve("unifier");
1721
public static final Path UNIFIER_DB_NAME = CONFIG_DIR.resolve("unifier-db.json");
1822
private static final Path CUSTOM_RULES_NAME = CONFIG_DIR.resolve("custom-rules-default.json");
1923

@@ -24,6 +28,7 @@ public void init() {
2428
try {
2529
Files.createDirectories(CONFIG_DIR);
2630
Files.createDirectories(RULES_DIR);
31+
Files.createDirectories(UNIFIER_DIR);
2732
} catch (IOException e) {
2833
FTBMaterials.LOGGER.error("can't create dir {} ! {}", CONFIG_DIR, e.getMessage());
2934
}
@@ -71,4 +76,19 @@ public UnifierDB unifierDB() {
7176
}
7277
return unifierDB;
7378
}
79+
80+
static void loadExtraJsonFiles(Path dir, Consumer<JsonElement> consumer) {
81+
try (Stream<Path> s = Files.list(dir)) {
82+
s.filter(p -> p.toString().endsWith(".json")).forEach(jsonFile -> {
83+
try {
84+
JsonElement json = JsonParser.parseString(Files.readString(jsonFile));
85+
consumer.accept(json);
86+
} catch (Exception ex) {
87+
FTBMaterials.LOGGER.error("can't read json file {}: {}", dir.resolve(jsonFile), ex.getMessage());
88+
}
89+
});
90+
} catch (IOException ex) {
91+
FTBMaterials.LOGGER.error("can't read directory {}: {}", dir, ex.getMessage());
92+
}
93+
}
7494
}

0 commit comments

Comments
 (0)