1.21 port (#86)

Co-authored-by: LLytho <main@lytho.dev>
This commit is contained in:
Relentless 2024-08-22 15:06:58 +02:00 committed by GitHub
parent a5146a487e
commit 14044f3b57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
243 changed files with 10664 additions and 4728 deletions

View file

@ -9,7 +9,7 @@ tab_width = 4
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_formatter_tags_enabled = true
ij_smart_tabs = false
ij_visual_guides = 120
ij_wrap_on_typing = false
@ -348,7 +348,7 @@ ij_kotlin_wrap_elvis_expressions = 1
ij_kotlin_wrap_expression_body_functions = 1
ij_kotlin_wrap_first_method_in_call_chain = false
[{**.json,mcmod.info,pack.mcmeta}]
[{*.json,mcmod.info,pack.mcmeta}]
indent_size = 2
max_line_length = 150
tab_width = 2
@ -365,7 +365,7 @@ ij_json_spaces_within_braces = false
ij_json_spaces_within_brackets = false
ij_json_wrap_long_lines = false
[**.md]
[*.md]
ij_visual_guides = none
ij_markdown_force_one_space_after_blockquote_symbol = true
ij_markdown_force_one_space_after_header_symbol = true
@ -379,11 +379,11 @@ ij_markdown_min_lines_around_block_elements = 1
ij_markdown_min_lines_around_header = 1
ij_markdown_min_lines_between_paragraphs = 1
[**.toml]
[*.toml]
ij_visual_guides = none
ij_toml_keep_indents_on_empty_lines = false
[{**.yml,**.yaml}]
[{*.yml,*.yaml}]
indent_size = 2
ij_visual_guides = none
ij_yaml_align_values_properties = do_not_align

View file

@ -1,62 +0,0 @@
name: Build
on:
workflow_dispatch:
push:
branches:
- '1.20.1'
tags-ignore:
- '**'
paths:
- 'gradle/**'
- '**.java'
- '**.kts'
- '**.properties'
- '**/build.yml'
pull_request:
branches:
- '1.20.1'
paths:
- 'gradle/**'
- '**.java'
- '**.kts'
- '**.properties'
- '**/build.yml'
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: 17
jobs:
build:
name: Build
runs-on: ubuntu-latest
if: |
!contains(github.event.head_commit.message, '[skip build]')
steps:
- name: Clone Repository
uses: actions/checkout@v3
- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v3
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DIST }}
cache: gradle
- name: Cleanup Gradle Cache
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties
- name: Make Gradle executable
run: chmod +x ./gradlew
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: Build
run: ./gradlew build --stacktrace
- name: Test
run: ./gradlew test --stacktrace

View file

@ -1,265 +0,0 @@
name: Release
on:
workflow_dispatch:
push:
tags:
- 'v1.20.1-*.*.*'
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: 17
MOD_ID: 'almostunified'
MOD_NAME: 'AlmostUnified'
CURSEFORGE_ID: '633823'
MODRINTH_ID: 'sdaSaQEz'
jobs:
build:
name: Build, collect info, parse changelog
runs-on: ubuntu-latest
outputs:
JAR_FILE: ${{ steps.collect_info.outputs.JAR_FILE }}
MINECRAFT_VERSION: ${{ steps.collect_info.outputs.MINECRAFT_VERSION }}
MOD_VERSION: ${{ steps.collect_info.outputs.MOD_VERSION }}
RELEASE_TYPE: ${{ steps.collect_info.outputs.RELEASE_TYPE }}
steps:
- name: Clone Repository
uses: actions/checkout@v3
- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v3
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DIST }}
cache: gradle
- name: Cleanup Gradle Cache
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties
- name: Make Gradle executable
run: chmod +x ./gradlew
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: Assemble the JARs
run: ./gradlew assemble
- name: Move JARs to central directory
run: |
mkdir output
mv -f Forge/build/libs/*.jar Fabric/build/libs/*.jar output/
rm -f output/*-dev-shadow.jar output/*-sources.jar
- name: Collect version information
id: collect_info
run: |
shopt -s failglob # print a warning if a glob does not match anything
set_var() {
echo $1="$2"
echo $1="$2" >> $GITHUB_OUTPUT
declare -g $1="$2"
}
set_var JAR_FILE $(eval echo output/${{ env.MOD_ID }}-*-*-*.jar)
set_var MINECRAFT_VERSION $(echo ${JAR_FILE%.*} | cut -d- -f3)
set_var MOD_VERSION $(echo ${JAR_FILE%.*} | cut -d- -f4)
set_var RELEASE_TYPE "$(echo ${GITHUB_REF##*/} | cut -d- -f3)"
set_var RELEASE_TYPE "$([[ -z $RELEASE_TYPE ]] && echo release || echo $RELEASE_TYPE)"
- name: Install changelog parser
uses: taiki-e/install-action@parse-changelog
- name: Parse changelog
run: parse-changelog CHANGELOG.md ${{ steps.collect_info.outputs.MOD_VERSION }} > output/changelog.md
- name: Archive results
run: tar -zcvf build.tar.gz output
- name: Upload results
uses: actions/upload-artifact@v3
with:
name: build-artifacts
path: build.tar.gz
if-no-files-found: error
retention-days: 3
- name: Job Summary
run: |
echo "# Version Information" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- Minecraft Version: ${{ steps.collect_info.outputs.MINECRAFT_VERSION }}" >> $GITHUB_STEP_SUMMARY
echo "- Mod Version: ${{ steps.collect_info.outputs.MOD_VERSION }}" >> $GITHUB_STEP_SUMMARY
echo "- Release Type: ${{ steps.collect_info.outputs.RELEASE_TYPE }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "# Build Information" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- JAR files: $(find output -maxdepth 1 -type f -name '*.jar' | wc -l)" >> $GITHUB_STEP_SUMMARY
echo "- Folder size: $(du -sh output | cut -f1)" >> $GITHUB_STEP_SUMMARY
echo "- Archive size: $(du -sh build.tar.gz | cut -f1)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "# Changelog" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
cat output/changelog.md >> $GITHUB_STEP_SUMMARY
mr-fabric-release:
name: Modrinth Fabric Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-artifacts
- name: Extract build artifacts
run: tar -zxvf build.tar.gz
- name: Release Fabric on Modrinth
uses: Kir-Antipov/mc-publish@v3.3
with:
modrinth-id: ${{ env.MODRINTH_ID }}
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
files: output/*fabric*.jar
name: ${{ env.MOD_NAME }}-Fabric-${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}
version: ${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}+fabric
version-type: ${{ needs.build.outputs.RELEASE_TYPE }}
changelog-file: output/changelog.md
loaders: fabric
game-versions: ${{ needs.build.outputs.MINECRAFT_VERSION }}
java: ${{ env.JAVA_VERSION }}
dependencies: |
jei(optional){curseforge:238222}{modrinth:u6dRKJwZ}
rei(optional){curseforge:310111}{modrinth:nfn13YXA}
cf-fabric-release:
name: CurseForge Fabric Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-artifacts
- name: Extract build artifacts
run: tar -zxvf build.tar.gz
- name: Release Fabric on CurseForge
uses: Kir-Antipov/mc-publish@v3.3
with:
curseforge-id: ${{ env.CURSEFORGE_ID }}
curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }}
files: output/*fabric*.jar
name: ${{ env.MOD_NAME }}-Fabric-${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}
version: ${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}+fabric
version-type: ${{ needs.build.outputs.RELEASE_TYPE }}
changelog-file: output/changelog.md
loaders: fabric
game-versions: ${{ needs.build.outputs.MINECRAFT_VERSION }}
java: ${{ env.JAVA_VERSION }}
dependencies: |
jei(optional){curseforge:238222}{modrinth:u6dRKJwZ}
rei(optional){curseforge:310111}{modrinth:nfn13YXA}
mr-forge-release:
name: Modrinth Forge Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-artifacts
- name: Extract build artifacts
run: tar -zxvf build.tar.gz
- name: Release Forge on Modrinth
uses: Kir-Antipov/mc-publish@v3.3
with:
modrinth-id: ${{ env.MODRINTH_ID }}
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
files: output/*forge*.jar
name: ${{ env.MOD_NAME }}-Forge-${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}
version: ${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}+forge
version-type: ${{ needs.build.outputs.RELEASE_TYPE }}
changelog-file: output/changelog.md
loaders: |
forge
neoforge
game-versions: ${{ needs.build.outputs.MINECRAFT_VERSION }}
java: ${{ env.JAVA_VERSION }}
dependencies: |
jei(optional){curseforge:238222}{modrinth:u6dRKJwZ}
rei(optional){curseforge:310111}{modrinth:nfn13YXA}
cf-forge-release:
name: CurseForge Forge Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-artifacts
- name: Extract build artifacts
run: tar -zxvf build.tar.gz
- name: Release Forge on CurseForge
uses: Kir-Antipov/mc-publish@v3.3
with:
curseforge-id: ${{ env.CURSEFORGE_ID }}
curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }}
files: output/*forge*.jar
name: ${{ env.MOD_NAME }}-Forge-${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}
version: ${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}+forge
version-type: ${{ needs.build.outputs.RELEASE_TYPE }}
changelog-file: output/changelog.md
loaders: |
forge
neoforge
game-versions: ${{ needs.build.outputs.MINECRAFT_VERSION }}
java: ${{ env.JAVA_VERSION }}
dependencies: |
jei(optional){curseforge:238222}{modrinth:u6dRKJwZ}
rei(optional){curseforge:310111}{modrinth:nfn13YXA}
github-release:
name: GitHub Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-artifacts
- name: Extract build artifacts
run: tar -zxvf build.tar.gz
- name: Release on GitHub
uses: Kir-Antipov/mc-publish@v3.3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
files: output/*.jar
name: v${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}
version: ${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}
version-type: ${{ needs.build.outputs.RELEASE_TYPE }}
changelog-file: output/changelog.md

View file

@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog],
and this project adheres to [Semantic Versioning].
## Unreleased
- Introduce data-driven like unification.
- `unify.json` moved into sub-folder `unify` to let the user define different unification rules.
- Custom tags, tag ownerships and tag inheritance moved into `tags.json`
- `material` config inside `unify` moved into `placeholders.json` to let the user define multiple different
placeholders
- `reiJeiHide` key in `unify` renamed to `recipeViewerHiding`
- Plugin system for mods to register their own unifiers
- Missing mods used in mod priorities will now be logged
- fixed a bug where stone stratas were not correctly identified (Fabric only)
- ignore `show_notification` and `category` recipe keys by default
## [0.7.2] - 2023-11-21
## Added

View file

@ -3,10 +3,10 @@ val minecraftVersion: String by project
val modPackage: String by project
val modId: String by project
val modName: String by project
val junitVersion: String by project
val fabricLoaderVersion: String by project
val jeiVersion: String by project
val reiVersion: String by project
val emiVersion: String by project
plugins {
id("com.github.gmazzo.buildconfig") version "4.0.4"
@ -29,18 +29,13 @@ dependencies {
* required here for the @Environment annotations and the mixin dependencies
* do NOT use other classes from the Fabric loader
*/
modImplementation("net.fabricmc:fabric-loader:$fabricLoaderVersion")
modCompileOnly("net.fabricmc:fabric-loader:$fabricLoaderVersion")
// compile time mods
// compile time
modCompileOnly("mezz.jei:jei-$minecraftVersion-common-api:$jeiVersion") // required for jei plugin
modCompileOnly("me.shedaniel:RoughlyEnoughItems-api:$reiVersion") // required for rei plugin
// compile time dependencies
compileOnly("me.shedaniel:REIPluginCompatibilities-forge-annotations:9.+") // required to disable rei compat layer
// tests
testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
modCompileOnly("dev.emi:emi-xplat-intermediary:$emiVersion+$minecraftVersion:api") // required for emi plugin
}
buildConfig {
@ -50,9 +45,3 @@ buildConfig {
packageName(modPackage)
useJavaOutput()
}
//tasks {
// withType<Test> {
// useJUnitPlatform()
// }
//}

View file

@ -1,98 +0,0 @@
package com.almostreliable.unified;
import com.almostreliable.unified.config.Config;
import com.almostreliable.unified.config.ServerConfigs;
import com.almostreliable.unified.config.StartupConfig;
import com.almostreliable.unified.config.UnifyConfig;
import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory;
import com.almostreliable.unified.utils.TagOwnerships;
import com.almostreliable.unified.utils.TagReloadHandler;
import com.google.common.base.Preconditions;
import com.google.gson.JsonElement;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Map;
@SuppressWarnings("UtilityClassWithoutPrivateConstructor")
public final class AlmostUnified {
public static final Logger LOG = LogManager.getLogger(BuildConfig.MOD_NAME);
@Nullable private static AlmostUnifiedRuntime RUNTIME;
@Nullable private static StartupConfig STARTUP_CONFIG;
public static boolean isRuntimeLoaded() {
return RUNTIME != null;
}
public static AlmostUnifiedRuntime getRuntime() {
if (RUNTIME == null) {
return AlmostUnifiedFallbackRuntime.getInstance();
}
return RUNTIME;
}
public static StartupConfig getStartupConfig() {
if (STARTUP_CONFIG == null) {
STARTUP_CONFIG = Config.load(StartupConfig.NAME, new StartupConfig.Serializer());
}
return STARTUP_CONFIG;
}
public static void onTagLoaderReload(Map<ResourceLocation, Collection<Holder<Item>>> tags) {
RecipeHandlerFactory recipeHandlerFactory = new RecipeHandlerFactory();
AlmostUnifiedPlatform.INSTANCE.bindRecipeHandlers(recipeHandlerFactory);
ServerConfigs serverConfigs = ServerConfigs.load();
UnifyConfig unifyConfig = serverConfigs.getUnifyConfig();
TagReloadHandler.applyCustomTags(unifyConfig);
TagOwnerships tagOwnerships = new TagOwnerships(
unifyConfig.bakeAndValidateTags(tags),
unifyConfig.getTagOwnerships()
);
tagOwnerships.applyOwnerships(tags);
ReplacementData replacementData = loadReplacementData(tags, unifyConfig, tagOwnerships);
RUNTIME = new AlmostUnifiedRuntimeImpl(
serverConfigs,
replacementData.filteredTagMap(),
replacementData.replacementMap(),
recipeHandlerFactory
);
}
public static void onRecipeManagerReload(Map<ResourceLocation, JsonElement> recipes) {
Preconditions.checkNotNull(RUNTIME, "AlmostUnifiedRuntime was not loaded correctly");
RUNTIME.run(recipes, getStartupConfig().isServerOnly());
}
/**
* Loads the required data for the replacement logic.
* <p>
* This method applies tag inheritance and rebuilds the replacement data if the
* inheritance mutates the tags.
*
* @param tags The vanilla tag map provided by the TagManager
* @param unifyConfig The mod config to use for unifying
* @param tagOwnerships The tag ownerships to apply
* @return The loaded data
*/
private static ReplacementData loadReplacementData(Map<ResourceLocation, Collection<Holder<Item>>> tags, UnifyConfig unifyConfig, TagOwnerships tagOwnerships) {
ReplacementData replacementData = ReplacementData.load(tags, unifyConfig, tagOwnerships);
var needsRebuild = TagReloadHandler.applyInheritance(unifyConfig, replacementData);
if (needsRebuild) {
return ReplacementData.load(tags, unifyConfig, tagOwnerships);
}
return replacementData;
}
}

View file

@ -0,0 +1,44 @@
package com.almostreliable.unified;
import com.almostreliable.unified.api.AlmostUnifiedRuntime;
import com.almostreliable.unified.config.Config;
import com.almostreliable.unified.config.StartupConfig;
import com.almostreliable.unified.core.AlmostUnifiedRuntimeImpl;
import com.almostreliable.unified.unification.loot.LootUnification;
import com.almostreliable.unified.utils.CustomLogger;
import com.almostreliable.unified.utils.VanillaTagWrapper;
import com.google.common.base.Preconditions;
import com.google.gson.JsonElement;
import net.minecraft.core.HolderLookup;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.Map;
@SuppressWarnings({ "UtilityClassWithoutPrivateConstructor", "StaticVariableUsedBeforeInitialization" })
public final class AlmostUnifiedCommon {
public static final Logger LOGGER = CustomLogger.create();
public static final StartupConfig STARTUP_CONFIG = Config.load(StartupConfig.NAME, StartupConfig.SERIALIZER);
@Nullable private static AlmostUnifiedRuntimeImpl RUNTIME;
@Nullable
public static AlmostUnifiedRuntime getRuntime() {
return RUNTIME;
}
public static void onTagLoaderReload(VanillaTagWrapper<Item> itemTags, VanillaTagWrapper<Block> blockTags) {
RUNTIME = AlmostUnifiedRuntimeImpl.create(itemTags, blockTags);
}
public static void onRecipeManagerReload(Map<ResourceLocation, JsonElement> recipes, HolderLookup.Provider registries) {
Preconditions.checkNotNull(RUNTIME, "AlmostUnifiedRuntime was not loaded correctly");
RUNTIME.run(recipes);
LootUnification.unifyLoot(RUNTIME, registries);
}
}

View file

@ -1,78 +0,0 @@
package com.almostreliable.unified;
import com.almostreliable.unified.api.StoneStrataHandler;
import com.almostreliable.unified.config.Config;
import com.almostreliable.unified.config.UnifyConfig;
import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMap;
import com.almostreliable.unified.utils.TagOwnerships;
import com.almostreliable.unified.utils.UnifyTag;
import com.google.gson.JsonElement;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
// TODO: Implement sync, so it's not just a fallback
public class AlmostUnifiedFallbackRuntime implements AlmostUnifiedRuntime {
@Nullable private static AlmostUnifiedFallbackRuntime INSTANCE;
@Nullable private UnifyConfig unifyConfig;
@Nullable private TagMap<Item> filteredTagMap;
@Nullable private ReplacementMap replacementMap;
public static AlmostUnifiedFallbackRuntime getInstance() {
if (INSTANCE == null) {
INSTANCE = new AlmostUnifiedFallbackRuntime();
INSTANCE.reload();
}
return INSTANCE;
}
public void reload() {
unifyConfig = null;
filteredTagMap = null;
replacementMap = null;
build();
}
private void build() {
unifyConfig = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer());
Set<UnifyTag<Item>> unifyTags = unifyConfig.bakeTags();
filteredTagMap = TagMap.create(unifyTags).filtered($ -> true, unifyConfig::includeItem);
StoneStrataHandler stoneStrataHandler = createStoneStrataHandler(unifyConfig);
TagOwnerships tagOwnerships = new TagOwnerships(unifyTags, unifyConfig.getTagOwnerships());
replacementMap = new ReplacementMap(unifyConfig, filteredTagMap, stoneStrataHandler, tagOwnerships);
}
private static StoneStrataHandler createStoneStrataHandler(UnifyConfig config) {
Set<UnifyTag<Item>> stoneStrataTags = AlmostUnifiedPlatform.INSTANCE.getStoneStrataTags(config.getStoneStrata());
TagMap<Item> stoneStrataTagMap = TagMap.create(stoneStrataTags);
return StoneStrataHandler.create(config.getStoneStrata(), stoneStrataTags, stoneStrataTagMap);
}
@Override
public void run(Map<ResourceLocation, JsonElement> recipes, boolean skipClientTracking) {
// no-op
}
@Override
public Optional<TagMap<Item>> getFilteredTagMap() {
return Optional.ofNullable(filteredTagMap);
}
@Override
public Optional<ReplacementMap> getReplacementMap() {
return Optional.ofNullable(replacementMap);
}
@Override
public Optional<UnifyConfig> getUnifyConfig() {
return Optional.ofNullable(unifyConfig);
}
}

View file

@ -1,87 +0,0 @@
package com.almostreliable.unified;
import com.almostreliable.unified.api.AlmostUnifiedLookup;
import com.almostreliable.unified.utils.UnifyTag;
import com.google.auto.service.AutoService;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.ItemLike;
import javax.annotation.Nullable;
import java.util.Set;
import java.util.stream.Collectors;
@AutoService(AlmostUnifiedLookup.class)
public class AlmostUnifiedLookupImpl implements AlmostUnifiedLookup {
@Override
public boolean isLoaded() {
return AlmostUnified.isRuntimeLoaded();
}
@Nullable
@Override
public Item getReplacementForItem(ItemLike itemLike) {
ResourceLocation id = BuiltInRegistries.ITEM.getKey(itemLike.asItem());
return AlmostUnified
.getRuntime()
.getReplacementMap()
.map(rm -> rm.getReplacementForItem(id))
.flatMap(BuiltInRegistries.ITEM::getOptional)
.orElse(null);
}
@Nullable
@Override
public Item getPreferredItemForTag(TagKey<Item> tag) {
UnifyTag<Item> asUnifyTag = UnifyTag.item(tag.location());
return AlmostUnified
.getRuntime()
.getReplacementMap()
.map(rm -> rm.getPreferredItemForTag(asUnifyTag, $ -> true))
.flatMap(BuiltInRegistries.ITEM::getOptional)
.orElse(null);
}
@Nullable
@Override
public TagKey<Item> getPreferredTagForItem(ItemLike itemLike) {
ResourceLocation id = BuiltInRegistries.ITEM.getKey(itemLike.asItem());
return AlmostUnified
.getRuntime()
.getReplacementMap()
.map(rm -> rm.getPreferredTagForItem(id))
.map(ut -> TagKey.create(Registries.ITEM, ut.location()))
.orElse(null);
}
@Override
public Set<Item> getPotentialItems(TagKey<Item> tag) {
UnifyTag<Item> asUnifyTag = UnifyTag.item(tag.location());
return AlmostUnified
.getRuntime()
.getFilteredTagMap()
.map(tagMap -> tagMap
.getEntriesByTag(asUnifyTag)
.stream()
.flatMap(rl -> BuiltInRegistries.ITEM.getOptional(rl).stream())
.collect(Collectors.toSet()))
.orElseGet(Set::of);
}
@Override
public Set<TagKey<Item>> getConfiguredTags() {
return AlmostUnified
.getRuntime()
.getFilteredTagMap()
.map(tagMap -> tagMap
.getTags()
.stream()
.map(ut -> TagKey.create(Registries.ITEM, ut.location()))
.collect(Collectors.toSet()))
.orElseGet(Set::of);
}
}

View file

@ -1,17 +1,13 @@
package com.almostreliable.unified;
import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory;
import com.almostreliable.unified.utils.UnifyTag;
import net.minecraft.world.item.Item;
import java.nio.file.Path;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
public interface AlmostUnifiedPlatform {
AlmostUnifiedPlatform INSTANCE = load(AlmostUnifiedPlatform.class);
AlmostUnifiedPlatform INSTANCE = ServiceLoader.load(AlmostUnifiedPlatform.class)
.findFirst()
.orElseThrow(() -> new NullPointerException("Failed to load platform service."));
/**
* Gets the current platform
@ -32,22 +28,10 @@ public interface AlmostUnifiedPlatform {
Path getConfigPath();
Path getLogPath();
void bindRecipeHandlers(RecipeHandlerFactory factory);
Set<UnifyTag<Item>> getStoneStrataTags(List<String> stoneStrataIds);
static <T> T load(Class<T> clazz) {
T loadedService = ServiceLoader.load(clazz)
.findFirst()
.orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName()));
AlmostUnified.LOG.debug("Loaded {} for service {}", loadedService, clazz);
return loadedService;
}
Path getDebugLogPath();
enum Platform {
FORGE,
NEO_FORGE,
FABRIC
}
}

View file

@ -1,22 +0,0 @@
package com.almostreliable.unified;
import com.almostreliable.unified.config.UnifyConfig;
import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMap;
import com.google.gson.JsonElement;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import java.util.Map;
import java.util.Optional;
public interface AlmostUnifiedRuntime {
void run(Map<ResourceLocation, JsonElement> recipes, boolean skipClientTracking);
Optional<TagMap<Item>> getFilteredTagMap();
Optional<ReplacementMap> getReplacementMap();
Optional<UnifyConfig> getUnifyConfig();
}

View file

@ -1,74 +0,0 @@
package com.almostreliable.unified;
import com.almostreliable.unified.config.DebugConfig;
import com.almostreliable.unified.config.DuplicationConfig;
import com.almostreliable.unified.config.ServerConfigs;
import com.almostreliable.unified.config.UnifyConfig;
import com.almostreliable.unified.recipe.RecipeDumper;
import com.almostreliable.unified.recipe.RecipeTransformer;
import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory;
import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMap;
import com.google.gson.JsonElement;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import java.util.Map;
import java.util.Optional;
public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime {
private final UnifyConfig unifyConfig;
private final DuplicationConfig duplicationConfig;
private final DebugConfig debugConfig;
private final TagMap<Item> tagMap;
private final ReplacementMap replacementMap;
private final RecipeHandlerFactory recipeHandlerFactory;
AlmostUnifiedRuntimeImpl(
ServerConfigs configs,
TagMap<Item> tagMap,
ReplacementMap repMap,
RecipeHandlerFactory recipeHandlerFactory
) {
this.unifyConfig = configs.getUnifyConfig();
this.duplicationConfig = configs.getDupConfig();
this.debugConfig = configs.getDebugConfig();
this.tagMap = tagMap;
this.replacementMap = repMap;
this.recipeHandlerFactory = recipeHandlerFactory;
}
@Override
public void run(Map<ResourceLocation, JsonElement> recipes, boolean skipClientTracking) {
debugConfig.logRecipes(recipes, "recipes_before_unification.txt");
debugConfig.logUnifyTagDump(tagMap);
long startTime = System.currentTimeMillis();
RecipeTransformer.Result result = new RecipeTransformer(
recipeHandlerFactory,
replacementMap,
unifyConfig,
duplicationConfig
).transformRecipes(recipes, skipClientTracking);
RecipeDumper dumper = new RecipeDumper(result, startTime, System.currentTimeMillis());
dumper.dump(debugConfig.dumpOverview, debugConfig.dumpUnification, debugConfig.dumpDuplicates);
debugConfig.logRecipes(recipes, "recipes_after_unification.txt");
}
@Override
public Optional<TagMap<Item>> getFilteredTagMap() {
return Optional.of(tagMap);
}
@Override
public Optional<ReplacementMap> getReplacementMap() {
return Optional.of(replacementMap);
}
@Override
public Optional<UnifyConfig> getUnifyConfig() {
return Optional.of(unifyConfig);
}
}

View file

@ -1,42 +0,0 @@
package com.almostreliable.unified;
import com.almostreliable.unified.api.StoneStrataHandler;
import com.almostreliable.unified.config.UnifyConfig;
import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMap;
import com.almostreliable.unified.utils.TagOwnerships;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import java.util.Collection;
import java.util.Map;
/**
* Holder class for storing all the data needed for replacements in recipes.
*
* @param globalTagMap The global tag map, containing all tags.
* @param filteredTagMap The filtered tag map, containing only the tags that will be used for replacing. Determined by the unify config.
* @param stoneStrataHandler The stone strata handler, used for replacing stone strata.
* @param replacementMap The replacement map, used for replacing items.
*/
public record ReplacementData(TagMap<Item> globalTagMap, TagMap<Item> filteredTagMap,
StoneStrataHandler stoneStrataHandler,
ReplacementMap replacementMap) {
public static ReplacementData load(Map<ResourceLocation, Collection<Holder<Item>>> tags, UnifyConfig unifyConfig, TagOwnerships tagOwnerships) {
var globalTagMap = TagMap.createFromItemTags(tags);
var unifyTags = unifyConfig.bakeAndValidateTags(tags);
var filteredTagMap = globalTagMap.filtered(unifyTags::contains, unifyConfig::includeItem);
var stoneStrataHandler = StoneStrataHandler.create(
unifyConfig.getStoneStrata(),
AlmostUnifiedPlatform.INSTANCE.getStoneStrataTags(unifyConfig.getStoneStrata()),
globalTagMap
);
var replacementMap = new ReplacementMap(unifyConfig, filteredTagMap, stoneStrataHandler, tagOwnerships);
return new ReplacementData(globalTagMap, filteredTagMap, stoneStrataHandler, replacementMap);
}
}

View file

@ -0,0 +1,168 @@
package com.almostreliable.unified.api;
import com.almostreliable.unified.api.unification.Placeholders;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.ItemLike;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.ServiceLoader;
import java.util.Set;
/**
* The core interface for the Almost Unified API.
* <p>
* Use this to get an instance of the {@link AlmostUnifiedRuntime} or to look up unification information.
*
* @since 1.0.0
*/
public interface AlmostUnified {
/**
* The default instance of Almost Unified.
* <p>
* If unavailable, it will return an empty instance that only returns default values for each method.<br>
* This instance is only available on the logical server side.
*/
@SuppressWarnings("InnerClassReferencedViaSubclass")
AlmostUnified INSTANCE = ServiceLoader.load(AlmostUnified.class).findFirst().orElseGet(Empty::new);
/**
* Returns whether the {@link AlmostUnifiedRuntime} is loaded and ready to be used.
* <p>
* The {@link AlmostUnifiedRuntime} is only available on the logical server side.
*
* @return true if the {@link AlmostUnifiedRuntime} is loaded, false otherwise
*/
boolean isRuntimeLoaded();
/**
* Returns the instance of the {@link AlmostUnifiedRuntime}.
* <p>
* The {@link AlmostUnifiedRuntime} is only available on the logical server side. This method returns null if the
* runtime is not loaded. To check this beforehand, use {@link #isRuntimeLoaded()}. If you are sure the runtime is
* available, you can use {@link #getRuntimeOrThrow()} instead.
*
* @return the {@link AlmostUnifiedRuntime}, or null if the runtime is not loaded
*/
@Nullable
AlmostUnifiedRuntime getRuntime();
/**
* Returns the instance of the {@link AlmostUnifiedRuntime}.
* <p>
* The {@link AlmostUnifiedRuntime} is only available on the logical server side. This method throws an exception
* if the runtime is not loaded. To check this beforehand, use {@link #isRuntimeLoaded()}.
*
* @return the {@link AlmostUnifiedRuntime}
*/
AlmostUnifiedRuntime getRuntimeOrThrow();
/**
* Returns all {@link TagKey}s used for the unification process.
* <p>
* The returned collection will only contain tags that have their {@link Placeholders} replaced and have been
* validated. All tags will be unique.
*
* @return the {@link TagKey}s used for the unification, or empty if no tags are used
*/
Collection<TagKey<Item>> getTags();
/**
* Returns all item entries for the given {@link TagKey}.
* <p>
* The returned collection will only contain entries if the provided {@link TagKey} is a configured unification tag.
*
* @param tag the {@link TagKey} to get the entries for
* @return the item entries for the {@link TagKey}, or empty if not found
*/
Collection<Item> getTagEntries(TagKey<Item> tag);
/**
* Returns the relevant {@link TagKey} for the given {@link ItemLike}
* <p>
* Since an item can only have a single relevant tag, this method is guaranteed to return a single {@link TagKey} as
* long as there exists a configured unification tag that includes the item.
*
* @param itemLike the {@link ItemLike} to get the relevant {@link TagKey} for
* @return the relevant {@link TagKey}, or null if not found
*/
@Nullable
TagKey<Item> getRelevantItemTag(ItemLike itemLike);
/**
* Returns the target item for the given variant {@link ItemLike}.
* <p>
* The target item describes the item with the highest priority among all variant items within a {@link TagKey}.
* It is used to replace the variant items in the unification process.<br>
* This method will return null if no configured unification tag exists that includes the given item.
* <p>
* If the item is part of a stone variant, it will only check items within the same stone variant.
*
* @param itemLike the variant {@link ItemLike} to get the target item for
* @return the target item, or null if not found
*/
@Nullable
Item getVariantItemTarget(ItemLike itemLike);
/**
* Returns the target item for the given {@link TagKey}.
* <p>
* The target item describes the item with the highest priority among all variant items within a {@link TagKey}.
* It is used to replace the variant items in the unification process.<br>
* This method will return null the given {@link TagKey} is not a configured unification tag.
*
* @param tag the {@link TagKey} to get the target item for
* @return the target item, or null if not found
*/
@Nullable
Item getTagTargetItem(TagKey<Item> tag);
class Empty implements AlmostUnified {
@Override
public boolean isRuntimeLoaded() {
return false;
}
@Nullable
@Override
public AlmostUnifiedRuntime getRuntime() {
return null;
}
@Override
public AlmostUnifiedRuntime getRuntimeOrThrow() {
throw new IllegalStateException("runtime is not loaded");
}
@Override
public Collection<TagKey<Item>> getTags() {
return Set.of();
}
@Override
public Collection<Item> getTagEntries(TagKey<Item> tag) {
return Set.of();
}
@Nullable
@Override
public TagKey<Item> getRelevantItemTag(ItemLike itemLike) {
return null;
}
@Nullable
@Override
public Item getVariantItemTarget(ItemLike itemLike) {
return null;
}
@Nullable
@Override
public Item getTagTargetItem(TagKey<Item> tag) {
return null;
}
}
}

View file

@ -1,106 +0,0 @@
package com.almostreliable.unified.api;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.ItemLike;
import javax.annotation.Nullable;
import java.util.ServiceLoader;
import java.util.Set;
public interface AlmostUnifiedLookup {
AlmostUnifiedLookup INSTANCE = ServiceLoader.load(AlmostUnifiedLookup.class).findFirst().orElseGet(Empty::new);
boolean isLoaded();
/**
* Returns the replacement item for a given {@link ItemLike}. Will return null if no configured
* tag exists that includes the item.
* <p>
* If the item is part of some stone strata, it will only check items within the same stone strata.<br>
* => e.g. "modid:deepslate_foo_ore" would not return "prio_modid:foo_ore".
*
* @param itemLike The item-like to find the replacement for
* @return The replacement item or null if there is no replacement
*/
@Nullable
Item getReplacementForItem(ItemLike itemLike);
/**
* Returns the preferred item for a given {@link TagKey}. Will return null if no configured
* tag exists that includes the item.
* <p>
* The preferred item is selected according to mod priorities, but it's possible to set a
* fixed override in the config.
*
* @param tag The tag to find the preferred item for
* @return The preferred item or null if there is no preferred item
*/
@Nullable
Item getPreferredItemForTag(TagKey<Item> tag);
/**
* Returns the preferred tag for a given {@link ItemLike} Will return null if no configured
* tag exists that includes the item.
*
* @param itemLike The item-like to find the preferred tag for
* @return The preferred tag or null if there is no preferred tag
*/
@Nullable
TagKey<Item> getPreferredTagForItem(ItemLike itemLike);
/**
* Returns all potential items which are part of a given tag.
* <p>
* Tags are only considered if they are part of the config,
* otherwise, an empty set is always returned.
*
* @param tag The tag to find the potential items for
* @return The potential items or an empty set if there are no potential items
*/
Set<Item> getPotentialItems(TagKey<Item> tag);
/**
* Returns all configured tags.
*
* @return The configured tags
*/
Set<TagKey<Item>> getConfiguredTags();
class Empty implements AlmostUnifiedLookup {
@Override
public boolean isLoaded() {
return false;
}
@Nullable
@Override
public Item getReplacementForItem(ItemLike itemLike) {
return null;
}
@Nullable
@Override
public Item getPreferredItemForTag(TagKey<Item> tag) {
return null;
}
@Nullable
@Override
public TagKey<Item> getPreferredTagForItem(ItemLike itemLike) {
return null;
}
@Override
public Set<Item> getPotentialItems(TagKey<Item> tag) {
return Set.of();
}
@Override
public Set<TagKey<Item>> getConfiguredTags() {
return Set.of();
}
}
}

View file

@ -0,0 +1,67 @@
package com.almostreliable.unified.api;
import com.almostreliable.unified.api.unification.Placeholders;
import com.almostreliable.unified.api.unification.TagSubstitutions;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.api.unification.UnificationSettings;
import javax.annotation.Nullable;
import java.util.Collection;
/**
* The runtime is the core of the Almost Unified mod.<br>
* It stores all required information about tags, recipes, unification settings, and configs.
* <p>
* The runtime is reconstructed every time the game reloads. Within the reconstruction process, all configs are reloaded,
* plugin unifiers are collected, tag changes are applied, and all lookups are recreated.
*
* @since 1.0.0
*/
public interface AlmostUnifiedRuntime {
/**
* Returns a composition of all {@link UnificationSettings}s.
* <p>
* Because {@link UnificationSettings}s include config-specific settings, and are thus not composable, the
* composition is returned as a {@link UnificationLookup}.
*
* @return the {@link UnificationSettings} composition as a {@link UnificationLookup}
*/
UnificationLookup getUnificationLookup();
/**
* Returns an unmodifiable collection of all {@link UnificationSettings}s.
*
* @return the {@link UnificationSettings} collection
*/
Collection<? extends UnificationSettings> getUnificationSettings();
/**
* Returns the {@link UnificationSettings} with the given name.
* <p>
* The name of a {@link UnificationSettings} is the name of the config file it was created from.
*
* @param name the name of the {@link UnificationSettings}
* @return the {@link UnificationSettings} with the given name or null if not found
*/
@Nullable
UnificationSettings getUnificationSettings(String name);
/**
* Returns the {@link TagSubstitutions} instance.
* <p>
* {@link TagSubstitutions} are defined in the {@code TagConfig}.
*
* @return the {@link TagSubstitutions}
*/
TagSubstitutions getTagSubstitutions();
/**
* Returns the {@link Placeholders} instance.
* <p>
* {@link Placeholders} are defined in the {@code PlaceholderConfig}.
*
* @return the {@link Placeholders}
*/
Placeholders getPlaceholders();
}

View file

@ -1,25 +0,0 @@
package com.almostreliable.unified.api;
@SuppressWarnings("SpellCheckingInspection")
public final class ModConstants {
// recipe viewers
public static final String JEI = "jei";
public static final String REI = "roughlyenoughitems";
// custom unify handlers
public static final String AD_ASTRA = "ad_astra";
public static final String AMETHYST_IMBUEMENT = "amethyst_imbuement";
public static final String ARS_CREO = "ars_creo";
public static final String ARS_ELEMENTAL = "ars_elemental";
public static final String ARS_NOUVEAU = "ars_nouveau";
public static final String ARS_SCALAES = "ars_scalaes";
public static final String CYCLIC = "cyclic";
public static final String ENDER_IO = "enderio";
public static final String GREGTECH_MODERN = "gtceu";
public static final String IMMERSIVE_ENGINEERING = "immersiveengineering";
public static final String INTEGRATED_DYNAMICS = "integrateddynamics";
public static final String MEKANISM = "mekanism";
public static final String MODERN_INDUSTRIALIZATION = "modern_industrialization";
private ModConstants() {}
}

View file

@ -1,107 +0,0 @@
package com.almostreliable.unified.api;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.utils.TagMap;
import com.almostreliable.unified.utils.UnifyTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import java.util.*;
import java.util.regex.Pattern;
public final class StoneStrataHandler {
private final List<String> stoneStrata;
private final Pattern tagMatcher;
private final TagMap<Item> stoneStrataTagMap;
// don't clear the caches, so they are available for the runtime and KubeJS binding
// the runtime holding this handler is automatically yeeted on reload
private final Map<UnifyTag<?>, Boolean> stoneStrataTagCache;
private final Map<ResourceLocation, String> stoneStrataCache;
private StoneStrataHandler(List<String> stoneStrata, Pattern tagMatcher, TagMap<Item> stoneStrataTagMap) {
this.stoneStrata = createSortedStoneStrata(stoneStrata);
this.tagMatcher = tagMatcher;
this.stoneStrataTagMap = stoneStrataTagMap;
this.stoneStrataTagCache = new HashMap<>();
this.stoneStrataCache = new HashMap<>();
}
/**
* Returns the stone strata list sorted from longest to shortest.
* <p>
* This is required to ensure that the longest strata is returned and no sub-matches happen.<br>
* Example: "nether" and "blue_nether" would both match "nether" if the list is not sorted.
*
* @param stoneStrata The stone strata list to sort.
* @return The sorted stone strata list.
*/
private static List<String> createSortedStoneStrata(List<String> stoneStrata) {
return stoneStrata.stream().sorted(Comparator.comparingInt(String::length).reversed()).toList();
}
public static StoneStrataHandler create(List<String> stoneStrataIds, Set<UnifyTag<Item>> stoneStrataTags, TagMap<Item> tagMap) {
var stoneStrataTagMap = tagMap.filtered(stoneStrataTags::contains, item -> true);
Pattern tagMatcher = Pattern.compile(switch (AlmostUnifiedPlatform.INSTANCE.getPlatform()) {
case FORGE -> "forge:ores/.+";
case FABRIC -> "(c:ores/.+|c:.+_ores)";
});
return new StoneStrataHandler(stoneStrataIds, tagMatcher, stoneStrataTagMap);
}
/**
* Returns the stone strata from the given item. Assumes that the item has a stone strata tag.
* Use {@link #isStoneStrataTag(UnifyTag)} to ensure this requirement.
*
* @param item The item to get the stone strata from.
* @return The stone strata of the item. Clean stone strata returns an empty string for later sorting as a
* fallback variant.
*/
public String getStoneStrata(ResourceLocation item) {
return stoneStrataCache.computeIfAbsent(item, this::computeStoneStrata);
}
/**
* Implementation logic for {@link #getStoneStrata(ResourceLocation)}.
*
* @param item The item to get the stone strata from.
* @return The stone strata of the item. Clean stone strata returns an empty string for later sorting as a
* fallback variant.
*/
private String computeStoneStrata(ResourceLocation item) {
String strata = stoneStrataTagMap
.getTagsByEntry(item)
.stream()
.findFirst()
.map(UnifyTag::location)
.map(ResourceLocation::toString)
.map(s -> {
int i = s.lastIndexOf('/');
return i == -1 ? null : s.substring(i + 1);
})
.orElse(null);
if (strata != null) {
if (strata.equals("stone")) {
return "";
}
return strata;
}
for (String stone : stoneStrata) {
if (item.getPath().contains(stone + "_")) {
if (stone.equals("stone")) {
return "";
}
return stone;
}
}
return "";
}
public boolean isStoneStrataTag(UnifyTag<Item> tag) {
return stoneStrataTagCache.computeIfAbsent(tag, t -> tagMatcher.matcher(t.location().toString()).matches());
}
}

View file

@ -0,0 +1,31 @@
package com.almostreliable.unified.api.constant;
import com.almostreliable.unified.BuildConfig;
@SuppressWarnings({ "SpellCheckingInspection", "StaticMethodOnlyUsedInOneClass" })
public interface ModConstants {
String ALMOST_UNIFIED = BuildConfig.MOD_ID;
// recipe viewer mods
String JEI = "jei";
String REI = "roughlyenoughitems";
String EMI = "emi";
// unify handler mods
String AMETHYST_IMBUEMENT = "amethyst_imbuement";
String ARS_CREO = "ars_creo";
String ARS_ELEMENTAL = "ars_elemental";
String ARS_NOUVEAU = "ars_nouveau";
String ARS_SCALAES = "ars_scalaes";
String CYCLIC = "cyclic";
String ENDER_IO = "enderio";
String GREGTECH_MODERN = "gtceu";
String IMMERSIVE_ENGINEERING = "immersiveengineering";
String INTEGRATED_DYNAMICS = "integrateddynamics";
String MEKANISM = "mekanism";
String MODERN_INDUSTRIALIZATION = "modern_industrialization";
String OCCULTISM = "occultism";
String PRODUCTIVE_TREES = "productivetrees";
String THEURGY = "theurgy";
}

View file

@ -0,0 +1,52 @@
package com.almostreliable.unified.api.constant;
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public interface RecipeConstants {
// inputs
String ITEM = "item";
String TAG = "tag";
String INPUT = "input";
String INPUTS = "inputs";
String INGREDIENT = "ingredient";
String INGREDIENTS = "ingredients";
String INPUT_ITEMS = "inputItems";
String CATALYST = "catalyst";
// outputs
String OUTPUT = "output";
String OUTPUTS = "outputs";
String RESULT = "result";
String RESULTS = "results";
String OUTPUT_ITEMS = "outputItems";
// inner keys
String VALUE = "value";
String BASE = "base";
String ID = "id";
// defaults
String[] DEFAULT_INPUT_KEYS = {
INPUT,
INPUTS,
INGREDIENT,
INGREDIENTS,
INPUT_ITEMS
};
String[] DEFAULT_INPUT_INNER_KEYS = {
VALUE,
BASE,
INGREDIENT
};
String[] DEFAULT_OUTPUT_KEYS = {
OUTPUT,
OUTPUTS,
RESULT,
RESULTS,
OUTPUT_ITEMS
};
String[] DEFAULT_OUTPUT_INNER_KEYS = {
ITEM,
INGREDIENT
};
}

View file

@ -1,5 +1,5 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.mixin.unifier;
package com.almostreliable.unified.api.constant;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

@ -0,0 +1,8 @@
package com.almostreliable.unified.api.plugin;
/**
* Annotation to use with {@link AlmostUnifiedPlugin} for NeoForge class discovery.
*
* @since 1.0.0
*/
public @interface AlmostUnifiedNeoPlugin {}

View file

@ -0,0 +1,36 @@
package com.almostreliable.unified.api.plugin;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifier;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry;
import net.minecraft.resources.ResourceLocation;
/**
* Implemented by plugins that wish to register their own {@link RecipeUnifier}s.
* <p>
* NeoForge plugins should attach the {@link AlmostUnifiedNeoPlugin} annotation for discovery.<br>
* Fabric plugins should use the {@code almostunified} entrypoint.
*
* @since 1.0.0
*/
public interface AlmostUnifiedPlugin {
/**
* Returns the identifier of the plugin.
* <p>
* If your mod has multiple plugins for different modules, make
* sure they are unique.
* <p>
* If you register a recipe unifier although Almost Unified already
* ships a recipe unifier for your recipes, yours will take precedence.
*
* @return the plugin id
*/
ResourceLocation getPluginId();
/**
* Allows registration of custom {@link RecipeUnifier}s.
*
* @param registry the {@link RecipeUnifierRegistry} to register with
*/
default void registerRecipeUnifiers(RecipeUnifierRegistry registry) {}
}

View file

@ -1,5 +1,5 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.api.recipe;
package com.almostreliable.unified.api.plugin;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

@ -1,55 +0,0 @@
package com.almostreliable.unified.api.recipe;
public final class RecipeConstants {
// common inputs
public static final String ITEM = "item";
public static final String TAG = "tag";
public static final String INPUT = "input";
public static final String INPUTS = "inputs";
public static final String INGREDIENT = "ingredient";
public static final String INGREDIENTS = "ingredients";
public static final String INPUT_ITEMS = "inputItems";
public static final String CATALYST = "catalyst";
// common outputs
public static final String OUTPUT = "output";
public static final String OUTPUTS = "outputs";
public static final String RESULT = "result";
public static final String RESULTS = "results";
public static final String OUTPUT_ITEMS = "outputItems";
// inner keys
public static final String VALUE = "value";
public static final String BASE = "base";
// ars nouveau
public static final String PEDESTAL_ITEMS = "pedestalItems";
public static final String REAGENT = "reagent";
// gregtech modern
public static final String TICK_INPUTS = "tickInputs";
public static final String TICK_OUTPUTS = "tickOutputs";
// immersive engineering
public static final String INPUT_0 = "input0";
public static final String INPUT_1 = "input1";
public static final String ADDITIVES = "additives";
public static final String SECONDARIES = "secondaries";
public static final String SLAG = "slag";
// mekanism
public static final String MAIN_INPUT = "mainInput";
public static final String MAIN_OUTPUT = "mainOutput";
public static final String ITEM_INPUT = "itemInput";
public static final String ITEM_OUTPUT = "itemOutput";
public static final String SECONDARY_OUTPUT = "secondaryOutput";
// modern industrialization
public static final String ITEM_INPUTS = "item_inputs";
public static final String ITEM_OUTPUTS = "item_outputs";
// cyclic
public static final String BONUS = "bonus";
private RecipeConstants() {}
}

View file

@ -1,38 +0,0 @@
package com.almostreliable.unified.api.recipe;
import com.almostreliable.unified.utils.UnifyTag;
import com.google.gson.JsonElement;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import javax.annotation.Nullable;
import java.util.function.Predicate;
public interface RecipeContext {
@Nullable
ResourceLocation getReplacementForItem(@Nullable ResourceLocation item);
@Nullable
ResourceLocation getPreferredItemForTag(@Nullable UnifyTag<Item> tag, Predicate<ResourceLocation> filter);
@Nullable
UnifyTag<Item> getPreferredTagForItem(@Nullable ResourceLocation item);
@Nullable
JsonElement createIngredientReplacement(@Nullable JsonElement element);
@Nullable
JsonElement createResultReplacement(@Nullable JsonElement element);
@Nullable
JsonElement createResultReplacement(@Nullable JsonElement element, boolean includeTagCheck, String... lookupKeys);
ResourceLocation getType();
boolean hasProperty(String property);
default String getModId() {
return getType().getNamespace();
}
}

View file

@ -1,5 +0,0 @@
package com.almostreliable.unified.api.recipe;
public interface RecipeUnifier {
void collectUnifier(RecipeUnifierBuilder builder);
}

View file

@ -1,15 +0,0 @@
package com.almostreliable.unified.api.recipe;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.function.BiFunction;
public interface RecipeUnifierBuilder {
void forEachObject(String property, BiFunction<JsonObject, RecipeContext, JsonObject> consumer);
void put(String property, BiFunction<JsonElement, RecipeContext, JsonElement> consumer);
<T extends JsonElement> void put(String property, Class<T> type, BiFunction<T, RecipeContext, T> consumer);
}

View file

@ -0,0 +1,70 @@
package com.almostreliable.unified.api.unification;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import javax.annotation.Nullable;
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Helper for handling mod priorities.
* <p>
* Mod priorities are used to choose the target items in the unification process.<br>
* If a tag contains multiple items from different mods, the priority defines which item is chosen first. Priority
* is sorted from highest to lowest. All unlisted mods have less priority than all listed mods.
* <p>
* Priority overrides allow overriding the priority mod for specific tags.<br>
* When a priority override is specified for a tag, the mod priorities will be ignored.
*
* @since 1.0.0
*/
public interface ModPriorities extends Iterable<String> {
/**
* Returns the priority override of the given tag.
* <p>
* This method returns the mod id if a priority override is configured for the given tag. If you want to resolve
* the tag to an item, use {@link #findPriorityOverrideItem(TagKey, List)} or
* {@link #findTargetItem(TagKey, List)} instead.
*
* @param tag the tag to get the priority override for
* @return the priority override, or null if no override exists
*/
@Nullable
String getPriorityOverride(TagKey<Item> tag);
/**
* Returns the priority override item of the given tag contained in the list of potential items.
* <p>
* This method returns the item if a priority override is configured for the given tag. If you want to resolve the
* tag to an item by also using the mod priorities, use {@link #findTargetItem(TagKey, List)} instead.
*
* @param tag the tag to get the priority override item for
* @param items the list of potential items, sorted from shortest to longest id
* @return the priority override item, or null if no override exists
*/
@Nullable
UnificationEntry<Item> findPriorityOverrideItem(TagKey<Item> tag, List<UnificationEntry<Item>> items);
/**
* Returns the target item of the given tag contained in the list of potential items.
* <p>
* The item is chosen according to the priority overrides first if available. If no priority override is configured,
* the item is chosen according to the mod priorities.
* <p>
* This method can return null if no override exists, and the potential items only include items with namespaces
* that are not part of the mod priorities.
*
* @param tag the tag to get the target item for
* @param items the list of potential items, sorted from shortest to longest id
* @return the target item of the given tag, or null if no target item could be found
*/
@Nullable
UnificationEntry<Item> findTargetItem(TagKey<Item> tag, List<UnificationEntry<Item>> items);
default Stream<String> stream() {
return StreamSupport.stream(spliterator(), false);
}
}

View file

@ -0,0 +1,52 @@
package com.almostreliable.unified.api.unification;
import java.util.Collection;
import java.util.function.BiConsumer;
/**
* Helper for handling placeholders in configs.
* <p>
* Placeholders are used to replace specific patterns in config values with a set of values to easily cover all possible
* combinations of all values. Placeholders are in the format of {@code {placeholder}}.
*
* @since 1.0.0
*/
public interface Placeholders {
/**
* Applies the placeholders to given string.
* <p>
* The given string is expected to contain an arbitrary number of placeholders or no placeholders at all.
* <p>
* This method replaces all contained placeholders with all combinations of possible values. If string doesn't
* contain any placeholders, the same string will be returned as the only element in the returned collection.
*
* @param str the string to apply the placeholders to
* @return a collection containing all combinations of applied placeholder values
*/
Collection<String> apply(String str);
/**
* Returns all placeholders as a collection.
*
* @return a collection containing all placeholders
*/
Collection<String> getPlaceholders();
/**
* Returns all possible replacements for given placeholder.
* <p>
* The possible replacement values are ensured to be unique.
*
* @param placeholder the placeholder to get replacements for
* @return a collection containing all possible replacements for the given placeholder
*/
Collection<String> getReplacements(String placeholder);
/**
* Passes each placeholder and its possible replacements to the given consumer.
*
* @param consumer the consumer to pass each placeholder and its possible replacements to
*/
void forEach(BiConsumer<String, Collection<String>> consumer);
}

View file

@ -0,0 +1,40 @@
package com.almostreliable.unified.api.unification;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
/**
* Helper to get the stone variant of an item.
* <p>
* Upon creation, this lookup will try to fetch the stone variant from the
* {@code c:ores_in_ground} tag. If the tag is present, it will always take priority.
* <p>
* As a fallback, it will lazily try to fetch the stone variant from the item or
* the respective block id.
*
* @since 1.0.0
*/
public interface StoneVariants {
/**
* Returns the stone variant for the given item.
* <p>
* This assumes that the item has a valid ore tag.<br>
* Use {@link #isOreTag(TagKey)} to ensure this requirement.
* <p>
* If the detected variant is stone, an empty string will be returned.
*
* @param item the item to get the stone variant from
* @return the stone variant of the item
*/
String getStoneVariant(ResourceLocation item);
/**
* Checks if the given tag is an ore tag.
*
* @param tag the tag to check
* @return true if the tag is an ore tag, false otherwise
*/
boolean isOreTag(TagKey<Item> tag);
}

View file

@ -0,0 +1,51 @@
package com.almostreliable.unified.api.unification;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Set;
/**
* Helper for tracking tag substitutions.
* <p>
* The tag substitutions system allows converting tags (replaced tags) to other tags (substitute tags).<br>
* The system copies all entries of the replaced tags to their respective substitute tags. After that, it replaces
* all occurrences of the replaced tags with their substitute tags in all recipes.
* <p>
* Example:<br>
* If we define the following entry {@code minecraft:logs -> minecraft:planks}, any recipes making use of the tag
* {@code minecraft:logs} will use the {@code minecraft:planks} tag instead.
* <p>
* This can be useful when mods use different tag conventions like {@code c:ingots/iron} and {@code c:iron_ingots}.
*
* @since 1.0.0
*/
public interface TagSubstitutions {
/**
* Returns the substitute tag for the provided replaced tag.
*
* @param replacedTag the replaced tag to get the substitute for
* @return the substitute tag or null if the provided tag is not a valid configured replaced tag
*/
@Nullable
TagKey<Item> getSubstituteTag(TagKey<Item> replacedTag);
/**
* Returns all replaced tags for the provided substitute tag.
*
* @param substituteTag the substitute tag to get the replaced tags for
* @return a collection of all replaced tags for the provided substitute tag or an empty collection if the
* provided tag is not a valid configured substitute tag
*/
Collection<TagKey<Item>> getReplacedTags(TagKey<Item> substituteTag);
/**
* Returns all valid configured replaced tags for all substitute tags.
*
* @return a collection of all valid configured replaced tags
*/
Set<TagKey<Item>> getReplacedTags();
}

View file

@ -0,0 +1,57 @@
package com.almostreliable.unified.api.unification;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
/**
* Helper to abstract a single entry used in unification.
* <p>
* This helper allows easy access to registry information while also offering utility methods.
*
* @param <T> the type of the entry
* @since 1.0.0
*/
public interface UnificationEntry<T> {
/**
* Returns the {@link ResourceKey} this entry is bound to in the {@link Registry}.
*
* @return the {@link ResourceKey} this entry is bound to
*/
ResourceKey<T> key();
/**
* Returns the id of this entry.
* <p>
* The id is the {@link ResourceLocation} of the entry in the {@link Registry}.
*
* @return the id of this entry
*/
ResourceLocation id();
/**
* Returns the raw value of this entry.
*
* @return the raw value
*/
T value();
/**
* Returns the tag of this entry.
* <p>
* The tag represents the relevant tag used for the unification. Each entry can only have a single unification tag.
*
* @return the tag
*/
TagKey<T> tag();
/**
* Returns the value as a {@link Holder.Reference}.
*
* @return the value holder
*/
Holder.Reference<T> asHolderOrThrow();
}

View file

@ -0,0 +1,242 @@
package com.almostreliable.unified.api.unification;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.function.Predicate;
/**
* Interface exposing composable unification information for a single unification config.
* <p>
* There exists one instance for each config.<br>
* A unification lookup only exposes composable information. The composition of all lookups is used to check global
* unification information. For example, when retrieving the replacement of a specific item. The composition can't
* expose internal information such as mod priorities.
* <p>
* If a lookup with exposed configuration is required, use {@link UnificationSettings} instead.
*
* @since 1.0.0
*/
public interface UnificationLookup {
/**
* Returns all {@link TagKey}s used for the unification process of the config this lookup is for.
* <p>
* The returned collection will only contain tags that have their {@link Placeholders} replaced and have been
* validated. All tags will be unique.
*
* @return the {@link TagKey}s used for the unification, or empty if no tags are used
*/
Collection<TagKey<Item>> getTags();
/**
* Returns all {@link UnificationEntry}s for the given {@link TagKey}.
* <p>
* The returned collection will only contain entries if the provided {@link TagKey} is part of the config this
* lookup is for.
*
* @param tag the {@link TagKey} to get the entries for
* @return the {@link UnificationEntry}s for the {@link TagKey}, or empty if no {@link UnificationEntry}s are found
*/
Collection<UnificationEntry<Item>> getTagEntries(TagKey<Item> tag);
/**
* Returns the {@link UnificationEntry} for the given item id.
* <p>
* If the config this lookup is for doesn't cover the {@link Item}, null is returned.
*
* @param item the item id to get the {@link UnificationEntry} for
* @return the {@link UnificationEntry} for the item id, or null if no {@link UnificationEntry} is found
*/
@Nullable
UnificationEntry<Item> getItemEntry(ResourceLocation item);
/**
* Returns the {@link UnificationEntry} for the given {@link Item}.
* <p>
* If the config this lookup is for doesn't cover the {@link Item}, null is returned.
*
* @param item the {@link Item} to get the {@link UnificationEntry} for
* @return the {@link UnificationEntry} for the {@link Item}, or null if no {@link UnificationEntry} is found
*/
@Nullable
default UnificationEntry<Item> getItemEntry(Item item) {
return getItemEntry(BuiltInRegistries.ITEM.getKey(item));
}
/**
* Returns the {@link UnificationEntry} for the given item {@link Holder}.
* <p>
* If the config this lookup is for doesn't cover the {@link Item}, null is returned.
*
* @param item the item {@link Holder} to get the {@link UnificationEntry} for
* @return the {@link UnificationEntry} for the item {@link Holder}, or null if no {@link UnificationEntry} is found
*/
@Nullable
default UnificationEntry<Item> getItemEntry(Holder<Item> item) {
return getItemEntry(item.value());
}
/**
* Returns the relevant {@link TagKey} for the given item id.
* <p>
* Since an item can only have a single relevant tag, this method is guaranteed to return a single {@link TagKey} as
* long as the config this lookup is for covers the {@link Item}.
*
* @param item the item id to get the relevant {@link TagKey} for
* @return the relevant {@link TagKey} for the item id, or null if no relevant {@link TagKey} is found
*/
@Nullable
TagKey<Item> getRelevantItemTag(ResourceLocation item);
/**
* Returns the relevant {@link TagKey} for the given {@link Item}.
* <p>
* Since an item can only have a single relevant tag, this method is guaranteed to return a single {@link TagKey} as
* long as the config this lookup is for covers the {@link Item}.
*
* @param item the {@link Item} to get the relevant {@link TagKey} for
* @return the relevant {@link TagKey} for the {@link Item}, or null if no relevant {@link TagKey} is found
*/
@Nullable
default TagKey<Item> getRelevantItemTag(Item item) {
return getRelevantItemTag(BuiltInRegistries.ITEM.getKey(item));
}
/**
* Returns the relevant {@link TagKey} for the given item {@link Holder}.
* <p>
* Since an item can only have a single relevant tag, this method is guaranteed to return a single {@link TagKey} as
* long as the config this lookup is for covers the {@link Item}.
*
* @param item the item {@link Holder} to get the relevant {@link TagKey} for
* @return the relevant {@link TagKey} for the item {@link Holder}, or null if no relevant {@link TagKey} is found
*/
@Nullable
default TagKey<Item> getRelevantItemTag(Holder<Item> item) {
return getRelevantItemTag(item.value());
}
/**
* Returns the target item {@link UnificationEntry} for the given variant item id.
* <p>
* The target item describes the item with the highest priority among all variant items within a tag. It is used
* to replace the variant items in the unification process.<br>
* This method will return null if the config this lookup is for doesn't cover a unification tag that includes
* the given item.
* <p>
* If the item is part of a stone variant, it will only check items within the same stone variant.
*
* @param item the variant item id to get the target {@link UnificationEntry} for
* @return the target {@link UnificationEntry} for the variant item id, or null if no target
* {@link UnificationEntry} is found
*/
@Nullable
UnificationEntry<Item> getVariantItemTarget(ResourceLocation item);
/**
* Returns the target item {@link UnificationEntry} for the given variant {@link Item}.
* <p>
* The target item describes the item with the highest priority among all variant items within a tag. It is used
* to replace the variant items in the unification process.<br>
* This method will return null if the config this lookup is for doesn't cover a unification tag that includes
* the given item.
* <p>
* If the item is part of a stone variant, it will only check items within the same stone variant.
*
* @param item the variant {@link Item} to get the target {@link UnificationEntry} for
* @return the target {@link UnificationEntry} for the variant {@link Item}, or null if no target
* {@link UnificationEntry} is found
*/
@Nullable
default UnificationEntry<Item> getVariantItemTarget(Item item) {
return getVariantItemTarget(BuiltInRegistries.ITEM.getKey(item));
}
/**
* Returns the target {@link UnificationEntry} for the given variant item {@link Holder}.
* <p>
* The target item describes the item with the highest priority among all variant items within a tag. It is used
* to replace the variant items in the unification process.<br>
* This method will return null if the config this lookup is for doesn't cover a unification tag that includes
* the given item.
* <p>
* If the item is part of a stone variant, it will only check items within the same stone variant.
*
* @param item the variant item {@link Holder} to get the target {@link UnificationEntry} for
* @return the target {@link UnificationEntry} for the variant item {@link Holder}, or null if no target
* {@link UnificationEntry} is found
*/
@Nullable
default UnificationEntry<Item> getVariantItemTarget(Holder<Item> item) {
return getVariantItemTarget(item.value());
}
/**
* Returns the target {@link UnificationEntry} for the given variant {@link UnificationEntry}.
* <p>
* The target item describes the item with the highest priority among all variant items within a {@link TagKey}.
* It is used to replace the variant items in the unification process.<br>
* This method will return null if the config this lookup is for doesn't cover a unification tag that includes
* the given item.
* <p>
* If the item is part of a stone variant, it will only check items within the same stone variant.
*
* @param item the variant {@link UnificationEntry} to get the target {@link UnificationEntry} for
* @return the target {@link UnificationEntry} for the variant {@link UnificationEntry}, or null if no target
* {@link UnificationEntry} is found
*/
@Nullable
default UnificationEntry<Item> getVariantItemTarget(UnificationEntry<Item> item) {
return getVariantItemTarget(item.asHolderOrThrow());
}
/**
* Returns the target {@link UnificationEntry} for the given {@link TagKey} that matches the given filter.
* <p>
* The target item describes the item with the highest priority among all variant items within a {@link TagKey}.
* It is used to replace the variant items in the unification process.<br>
* If the config this lookup is for doesn't cover the {@link TagKey}, null is returned.
*
* @param tag the {@link TagKey} to get the target {@link UnificationEntry} for
* @return the target {@link UnificationEntry} for the {@link TagKey}, or null if no target
* {@link UnificationEntry} is found
*/
@Nullable
UnificationEntry<Item> getTagTargetItem(TagKey<Item> tag, Predicate<ResourceLocation> itemFilter);
/**
* Returns the target {@link UnificationEntry} for the given {@link TagKey}.
* <p>
* The target item describes the item with the highest priority among all variant items within a {@link TagKey}.
* It is used to replace the variant items in the unification process.<br>
* If the config this lookup is for doesn't cover the {@link TagKey}, null is returned.
*
* @param tag the {@link TagKey} to get the target {@link UnificationEntry} for
* @return the target {@link UnificationEntry} for the {@link TagKey}, or null if no target
* {@link UnificationEntry} is found
*/
@Nullable
default UnificationEntry<Item> getTagTargetItem(TagKey<Item> tag) {
return getTagTargetItem(tag, $ -> true);
}
/**
* Returns whether the given {@link ItemStack} is part of any tags the given {@link Ingredient} points to.
* <p>
* To check this, this method fetches all unification tags of the {@link Item}s within the given {@link Ingredient}
* and checks whether the given {@link ItemStack} is part of them.
*
* @param ingredient the {@link Ingredient} to check to get the relevant tags for
* @param item the {@link ItemStack} to check
* @return whether the given {@link ItemStack} is part of any tags the given {@link Ingredient} points to
*/
boolean isUnifiedIngredientItem(Ingredient ingredient, ItemStack item);
}

View file

@ -0,0 +1,110 @@
package com.almostreliable.unified.api.unification;
import com.almostreliable.unified.api.unification.recipe.RecipeData;
import net.minecraft.resources.ResourceLocation;
/**
* Interface exposing unification information for a single unification config.
* <p>
* There exists one instance for each config.<br>
* Because {@link UnificationLookup}s are not composable, this interface should only be used when specific settings
* need to be checked.
*
* @since 1.0.0
*/
public interface UnificationSettings extends UnificationLookup {
/**
* Returns the name of the unification config these settings are for.
* <p>
* The name of the config is the file name of the JSON file within the unification subfolder without the file
* extension.
*
* @return the name of the config
*/
String getName();
/**
* Returns the instance of the {@link ModPriorities} these settings are based on.
*
* @return the {@link ModPriorities}
*/
ModPriorities getModPriorities();
/**
* Returns the instance of the {@link StoneVariants} these settings are based on.
*
* @return the {@link StoneVariants}
*/
StoneVariants getStoneVariants();
/**
* Returns whether the given {@link RecipeData} should be included in the unification process.
* <p>
* This method is a quick way to check the recipe id and type.
*
* @param recipe the recipe to check
* @return true if the recipe should be included, false otherwise
*/
default boolean shouldIncludeRecipe(RecipeData recipe) {
return shouldIncludeRecipeType(recipe) && shouldIncludeRecipeId(recipe);
}
/**
* Returns whether the given recipe type should be included in the unification process.
*
* @param type the recipe type to check
* @return true if the recipe type should be included, false otherwise
*/
boolean shouldIncludeRecipeType(ResourceLocation type);
/**
* Returns whether the recipe type of the given {@link RecipeData} should be included in the unification process.
*
* @param recipe the recipe to check
* @return true if the recipe type should be included, false otherwise
*/
default boolean shouldIncludeRecipeType(RecipeData recipe) {
return shouldIncludeRecipeType(recipe.getType());
}
/**
* Returns whether the given recipe id should be included in the unification process.
*
* @param id the recipe id to check
* @return true if the recipe id should be included, false otherwise
*/
boolean shouldIncludeRecipeId(ResourceLocation id);
/**
* Returns whether the recipe id of the given {@link RecipeData} should be included in the unification process.
*
* @param recipe the recipe to check
* @return true if the recipe id should be included, false otherwise
*/
default boolean shouldIncludeRecipeId(RecipeData recipe) {
return shouldIncludeRecipeId(recipe.getId());
}
/**
* Returns whether variant items of this config should be hidden in recipe viewers.
*
* @return true if variant items should be hidden, false otherwise
*/
boolean shouldHideVariantItems();
/**
* Returns whether loot tables should be unified with the unification of this config.
*
* @return true if loot tables should be unified, false otherwise
*/
boolean shouldUnifyLoot();
/**
* Returns whether the given loot table should be included in the unification process.
*
* @param table the loot table to check
* @return true if the loot table should be included, false otherwise
*/
boolean shouldIncludeLootTable(ResourceLocation table);
}

View file

@ -0,0 +1,41 @@
package com.almostreliable.unified.api.unification.bundled;
import com.almostreliable.unified.api.constant.RecipeConstants;
import com.almostreliable.unified.api.unification.recipe.RecipeJson;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifier;
import com.almostreliable.unified.api.unification.recipe.UnificationHelper;
/**
* The most basic {@link RecipeUnifier} implementation.
* <p>
* This {@link RecipeUnifier} will only be used if no other {@link RecipeUnifier} is registered for a recipe and more
* specific {@link RecipeUnifier}s such as the {@link ShapedRecipeUnifier} or {@link SmithingRecipeUnifier} can not be
* applied.<br>
* It targets the most basic and commonly used keys and structures for inputs and outputs.
* <p>
* Custom {@link RecipeUnifier}s can call {@link GenericRecipeUnifier#unify(UnificationHelper, RecipeJson)} on the
* {@link GenericRecipeUnifier#INSTANCE} to apply the defaults.
* <p>
* For more specific {@link RecipeUnifier} implementations, see {@link ShapedRecipeUnifier} and
* {@link SmithingRecipeUnifier}.
*
* @since 1.0.0
*/
public final class GenericRecipeUnifier implements RecipeUnifier {
public static final GenericRecipeUnifier INSTANCE = new GenericRecipeUnifier();
@Override
public void unify(UnificationHelper helper, RecipeJson recipe) {
unifyInputs(helper, recipe);
unifyOutputs(helper, recipe);
}
public void unifyInputs(UnificationHelper helper, RecipeJson recipe) {
helper.unifyInputs(recipe, RecipeConstants.DEFAULT_INPUT_KEYS);
}
public void unifyOutputs(UnificationHelper helper, RecipeJson recipe) {
helper.unifyOutputs(recipe, RecipeConstants.DEFAULT_OUTPUT_KEYS);
}
}

View file

@ -0,0 +1,63 @@
package com.almostreliable.unified.api.unification.bundled;
import com.almostreliable.unified.api.constant.RecipeConstants;
import com.almostreliable.unified.api.unification.recipe.RecipeData;
import com.almostreliable.unified.api.unification.recipe.RecipeJson;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifier;
import com.almostreliable.unified.api.unification.recipe.UnificationHelper;
import com.google.gson.JsonObject;
import net.minecraft.resources.ResourceLocation;
/**
* The {@link RecipeUnifier} implementation for shaped crafting recipes.
* <p>
* This {@link RecipeUnifier} will only be used if no other {@link RecipeUnifier} is registered for a recipe. It targets
* vanilla shaped crafting recipes and custom recipe types that use common properties of shaped crafting recipes.<br>
* If this {@link RecipeUnifier} can't be applied for a recipe, the {@link GenericRecipeUnifier} will be used as the
* last fallback.
* <p>
* To check if a recipe is applicable for this {@link RecipeUnifier}, use
* {@link ShapedRecipeUnifier#isApplicable(RecipeData)}. Custom {@link RecipeUnifier}s can call
* {@link ShapedRecipeUnifier#unify(UnificationHelper, RecipeJson)} on the {@link ShapedRecipeUnifier#INSTANCE} to apply
* the defaults.
*
* @since 1.0.0
*/
public final class ShapedRecipeUnifier implements RecipeUnifier {
public static final RecipeUnifier INSTANCE = new ShapedRecipeUnifier();
public static final ResourceLocation SHAPED_TYPE = ResourceLocation.withDefaultNamespace("crafting_shaped");
public static final String KEY_PROPERTY = "key";
public static final String PATTERN_PROPERTY = "pattern";
@Override
public void unify(UnificationHelper helper, RecipeJson recipe) {
GenericRecipeUnifier.INSTANCE.unify(helper, recipe);
if (recipe.getProperty(KEY_PROPERTY) instanceof JsonObject json) {
for (var e : json.entrySet()) {
helper.unifyInputElement(e.getValue());
}
}
}
/**
* Checks if this {@link RecipeUnifier} can be applied for the given {@link RecipeData}.
* <p>
* This method checks for the vanilla shaped crafting recipe type. If it's a custom recipe type, it tries to find
* common keys for the shaped crafting recipe.
*
* @param recipe the recipe to check
* @return true if the {@link RecipeUnifier} can be applied, false otherwise
*/
public static boolean isApplicable(RecipeData recipe) {
return recipe.getType().equals(SHAPED_TYPE) || hasShapedCraftingLikeStructure(recipe);
}
@SuppressWarnings("FoldExpressionIntoStream")
private static boolean hasShapedCraftingLikeStructure(RecipeData recipe) {
return recipe.hasProperty(KEY_PROPERTY) &&
recipe.hasProperty(PATTERN_PROPERTY) &&
recipe.hasProperty(RecipeConstants.RESULT);
}
}

View file

@ -0,0 +1,61 @@
package com.almostreliable.unified.api.unification.bundled;
import com.almostreliable.unified.api.constant.RecipeConstants;
import com.almostreliable.unified.api.unification.recipe.RecipeData;
import com.almostreliable.unified.api.unification.recipe.RecipeJson;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifier;
import com.almostreliable.unified.api.unification.recipe.UnificationHelper;
import net.minecraft.resources.ResourceLocation;
/**
* The {@link RecipeUnifier} implementation for smithing recipes.
* <p>
* This {@link RecipeUnifier} will only be used if no other {@link RecipeUnifier} is registered for a recipe. It targets
* vanilla smithing recipes and custom recipe types that use common properties of smithing recipes.<br>
* If this {@link RecipeUnifier} can't be applied for a recipe, the {@link GenericRecipeUnifier} will be used as the
* last fallback.
* <p>
* To check if a recipe is applicable for this {@link RecipeUnifier}, use
* {@link SmithingRecipeUnifier#isApplicable(RecipeData)}. Custom {@link RecipeUnifier}s can call
* {@link SmithingRecipeUnifier#unify(UnificationHelper, RecipeJson)} on the {@link SmithingRecipeUnifier#INSTANCE} to
* apply the defaults.
*
* @since 1.0.0
*/
public final class SmithingRecipeUnifier implements RecipeUnifier {
public static final SmithingRecipeUnifier INSTANCE = new SmithingRecipeUnifier();
public static final ResourceLocation TRANSFORM_TYPE = ResourceLocation.withDefaultNamespace("smithing_transform");
public static final ResourceLocation TRIM_TYPE = ResourceLocation.withDefaultNamespace("smithing_trim");
public static final String ADDITION_PROPERTY = "addition";
public static final String BASE_PROPERTY = "base";
public static final String TEMPLATE_PROPERTY = "template";
@Override
public void unify(UnificationHelper helper, RecipeJson recipe) {
GenericRecipeUnifier.INSTANCE.unify(helper, recipe);
helper.unifyInputs(recipe, ADDITION_PROPERTY, BASE_PROPERTY, TEMPLATE_PROPERTY);
}
/**
* Checks if this {@link RecipeUnifier} can be applied for the given {@link RecipeData}.
* <p>
* This method checks for the vanilla smithing recipe type. If it's a custom recipe type, it tries to find common
* keys for the smithing recipe.
*
* @param recipe the recipe to check
* @return true if the {@link RecipeUnifier} can be applied, false otherwise
*/
public static boolean isApplicable(RecipeData recipe) {
return recipe.getType().equals(TRANSFORM_TYPE) ||
recipe.getType().equals(TRIM_TYPE) ||
hasSmithingLikeStructure(recipe);
}
@SuppressWarnings("FoldExpressionIntoStream")
private static boolean hasSmithingLikeStructure(RecipeData recipe) {
return recipe.hasProperty(ADDITION_PROPERTY) &&
recipe.hasProperty(BASE_PROPERTY) &&
recipe.hasProperty(RecipeConstants.RESULT);
}
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.api.unification.bundled;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.api.unification;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,33 @@
package com.almostreliable.unified.api.unification.recipe;
import net.minecraft.resources.ResourceLocation;
/**
* Basic information about a recipe used for determination of the correct {@link RecipeUnifier}.
*
* @since 1.0.0
*/
public interface RecipeData {
/**
* Returns the recipe id as a {@link ResourceLocation}.
*
* @return the id
*/
ResourceLocation getId();
/**
* Returns the recipe type as a {@link ResourceLocation}.
*
* @return the recipe type
*/
ResourceLocation getType();
/**
* Checks if the current recipe contains the property with the given key.
*
* @param key the key of the property to check for
* @return true if the recipe contains the property, false otherwise
*/
boolean hasProperty(String key);
}

View file

@ -0,0 +1,30 @@
package com.almostreliable.unified.api.unification.recipe;
import com.google.gson.JsonElement;
import javax.annotation.Nullable;
/**
* Abstraction of a recipe JSON to access and override properties.
*
* @since 1.0.0
*/
public interface RecipeJson extends RecipeData {
/**
* Returns the value of the property with the given key.
*
* @param key the key to retrieve the property for
* @return the property value or null if not present
*/
@Nullable
JsonElement getProperty(String key);
/**
* Sets the property with the given key to the given value.
*
* @param key the key to set the property for
* @param value the value to set
*/
void setProperty(String key, JsonElement value);
}

View file

@ -0,0 +1,34 @@
package com.almostreliable.unified.api.unification.recipe;
import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin;
import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier;
/**
* Implemented on custom recipe unifiers.
* <p>
* Custom unifiers will tell Almost Unified how to handle specific recipes.<br>
* It can provide information about custom recipe keys not covered by the default unifiers and how to
* treat them. Whether they support ingredient replacements or just items.<br>
* Recipes will be marked as modified automatically through comparison with the original JSON.
* <p>
* Unifiers can either be registered per recipe type or per mod id. Registering a custom unifier will
* disable the default unifiers such as the {@link GenericRecipeUnifier}.
* <p>
* Registration is handled in {@link RecipeUnifierRegistry} which can be obtained in
* {@link AlmostUnifiedPlugin#registerRecipeUnifiers(RecipeUnifierRegistry)}.
*
* @since 1.0.0
*/
public interface RecipeUnifier {
/**
* Uses of the given {@link UnificationHelper} to unify the given {@link RecipeJson}.
* <p>
* {@link RecipeJson} is a utility wrapper that allows to easily access recipe information such as the recipe id,
* the recipe type and provides methods to check or modify the raw JSON.
*
* @param helper the helper to aid in the unification
* @param recipe the recipe to unify as a {@link RecipeJson}
*/
void unify(UnificationHelper helper, RecipeJson recipe);
}

View file

@ -0,0 +1,47 @@
package com.almostreliable.unified.api.unification.recipe;
import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier;
import net.minecraft.resources.ResourceLocation;
/**
* The registry holding all {@link RecipeUnifier}s.
* <p>
* {@link RecipeUnifier}s can be registered per recipe type or per mod id.
*
* @since 1.0.0
*/
public interface RecipeUnifierRegistry {
/**
* Registers a {@link RecipeUnifier} for a specific recipe type.
* <p>
* Recipe-type-based recipe unifiers override mod-id-based recipe unifiers.<br>
* Registering a custom recipe unifier will always disable the bundled recipe unifiers
* like the {@link GenericRecipeUnifier}.
*
* @param recipeType the recipe type to register the recipe unifier for
* @param recipeUnifier the recipe unifier
*/
void registerForRecipeType(ResourceLocation recipeType, RecipeUnifier recipeUnifier);
/**
* Registers a {@link RecipeUnifier} for a specific mod id.
* <p>
* Mod-id-based recipe unifiers will only apply if no recipe-type-based recipe unifiers
* are registered for the respective recipe.<br>
* Registering a custom recipe unifier will always disable the bundled recipe unifiers
* like the {@link GenericRecipeUnifier}.
*
* @param modId the mod id to register the recipe unifier for
* @param recipeUnifier the recipe unifier
*/
void registerForModId(String modId, RecipeUnifier recipeUnifier);
/**
* Retrieves the respective {@link RecipeUnifier} for the given {@link RecipeData}.
*
* @param recipeData the recipe data
* @return the recipe unifier for the given recipe data
*/
RecipeUnifier getRecipeUnifier(RecipeData recipeData);
}

View file

@ -0,0 +1,247 @@
package com.almostreliable.unified.api.unification.recipe;
import com.almostreliable.unified.api.constant.RecipeConstants;
import com.almostreliable.unified.api.unification.TagSubstitutions;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import javax.annotation.Nullable;
/**
* Helper interface to aid in the unification of recipes.
* <p>
* This interface provides methods to unify elements within recipes. Unification involves converting elements to tags
* or target items<br>
* An instance of this interface is passed to {@link RecipeUnifier#unify(UnificationHelper, RecipeJson)} to assist in
* the unification process of the given {@link RecipeJson}.
* <p>
* Implementations of this interface are expected to provide the logic for unification, typically based on predefined
* lookup tables or constants. The methods provided by this interface handle various JSON structures, including
* {@link JsonObject}s, {@link JsonArray}s, and individual {@link JsonElement}s.
*
* @since 1.0.0
*/
public interface UnificationHelper {
/**
* Returns the instance of the {@link UnificationLookup} this helper is based on.
*
* @return the {@link UnificationLookup} this helper is based on
*/
UnificationLookup getUnificationLookup();
/**
* Fetches all entries of the given {@link RecipeJson} under the specified keys and unifies them as inputs.<br>
* Entries treated as inputs will be converted to tags if possible.
* <p>
* The keys refer to top-level entries in the {@link RecipeJson}. This method requires at least one key to be
* provided.<br>
* To use default keys, refer to {@link RecipeConstants#DEFAULT_INPUT_KEYS} or see {@link GenericRecipeUnifier}.
*
* @param recipe the {@link RecipeJson} to fetch the input entries from
* @param keys the keys of the input entries to unify
* @return true if any element was changed, false otherwise
*/
boolean unifyInputs(RecipeJson recipe, String... keys);
/**
* Unifies a {@link JsonElement} as an input.<br>
* Elements treated as inputs will be converted to tags if possible.
* <p>
* This method can unify {@link JsonObject}s and {@link JsonArray}s.<br>
* The keys will be used for each nested element. If no keys are provided, it falls back to
* {@link RecipeConstants#DEFAULT_INPUT_INNER_KEYS}.
*
* @param jsonElement the {@link JsonElement} to unify
* @param keys the keys to use
* @return true if the {@link JsonElement} was changed, false otherwise
*/
boolean unifyInputElement(@Nullable JsonElement jsonElement, String... keys);
/**
* Unifies a {@link JsonArray} as an input.<br>
* Elements treated as inputs will be converted to tags if possible.
* <p>
* The keys will be used for each nested element. If no keys are provided, it falls back to
* {@link RecipeConstants#DEFAULT_INPUT_INNER_KEYS}.
*
* @param jsonArray the {@link JsonArray} to unify
* @param keys the keys to use
* @return true if any element of the {@link JsonArray} was changed, false otherwise
*/
boolean unifyInputArray(JsonArray jsonArray, String... keys);
/**
* Unifies a {@link JsonObject} as an input.<br>
* Elements treated as inputs will be converted to tags if possible.
* <p>
* The keys will be used for each nested element. If no keys are provided, it falls back to
* {@link RecipeConstants#DEFAULT_INPUT_INNER_KEYS}.
*
* @param jsonObject the {@link JsonObject} to unify
* @param keys the keys to use
* @return true if any element of the {@link JsonObject} was changed, false otherwise
*/
boolean unifyInputObject(JsonObject jsonObject, String... keys);
/**
* Unifies a {@link JsonObject} as a tag input.<br>
* Tag inputs are only changed if they have an associated {@link TagSubstitutions} entry.
*
* @param jsonObject the {@link JsonObject} to unify
* @return true if the tag input was changed, false otherwise
*/
boolean unifyInputTag(JsonObject jsonObject);
/**
* Unifies a {@link JsonObject} as an item input.<br>
* The item will be converted to a tag if possible.
*
* @param jsonObject the {@link JsonObject} to unify
* @return true if the item input was changed, false otherwise
*/
boolean unifyInputItem(JsonObject jsonObject);
/**
* Fetches all entries of the given {@link RecipeJson} under the specified keys and unifies them as
* outputs.<br>
* Entries treated as outputs will be converted to target items. If the entry is a tag, it will be converted
* to the target item of the tag.
* <p>
* The keys refer to top-level entries in the {@link RecipeJson}. This method requires at least one key to be
* provided.<br>
* To use default keys, refer to {@link RecipeConstants#DEFAULT_OUTPUT_KEYS} or see {@link GenericRecipeUnifier}.
*
* @param recipe the {@link RecipeJson} to fetch the output entries from
* @param keys the keys of the output entries to unify
* @return true if any element was changed, false otherwise
*/
boolean unifyOutputs(RecipeJson recipe, String... keys);
/**
* Fetches all entries of the given {@link RecipeJson} under the specified keys and unifies them as
* outputs.<br>
* Entries treated as outputs will be converted to target items. If the entry is a tag and tagsToItems is true,
* it will be converted to the target item of the tag.
* <p>
* The keys refer to top-level entries in the {@link RecipeJson}. This method requires at least one key to be
* provided.<br>
* To use default keys, refer to {@link RecipeConstants#DEFAULT_OUTPUT_KEYS} or see {@link GenericRecipeUnifier}.
*
* @param recipe the {@link RecipeJson} to fetch the output entries from
* @param tagsToItems if true, tags will be converted to target items
* @param keys the keys of the output entries to unify
* @return true if any element was changed, false otherwise
*/
boolean unifyOutputs(RecipeJson recipe, boolean tagsToItems, String... keys);
/**
* Fetches the entry of the given {@link RecipeJson} under the specified key and unifies it as output by using the
* given inner keys.<br>
* Entries treated as outputs will be converted to target items. If the entry is a tag and tagsToItems is true,
* it will be converted to the target item of the tag.
* <p>
* The key refers to a top-level entry in the {@link RecipeJson} while the inner keys refer to nested entries within
* the resulting {@link JsonElement}.
*
* @param recipe the {@link RecipeJson} to fetch the output entry from
* @param key the key of the output entry to unify
* @param tagsToItems if true, tags will be converted to target items
* @param innerKeys the inner keys of the output entry to unify
* @return true if any element was changed, false otherwise
*/
boolean unifyOutputs(RecipeJson recipe, String key, boolean tagsToItems, String... innerKeys);
/**
* Unifies a {@link JsonElement} as an output.<br>
* Elements treated as outputs will be converted to target items. If the element is a tag and tagsToItems is true,
* it will be converted to the target item of the tag.
* <p>
* This method can unify {@link JsonObject}s and {@link JsonArray}s.<br>
* The keys will be used for each nested element. If no keys are provided, it falls back to
* {@link RecipeConstants#DEFAULT_OUTPUT_INNER_KEYS}.
*
* @param jsonElement the {@link JsonElement} to unify
* @param tagsToItems if true, tags will be converted to target items
* @param keys the keys to use
* @return true if the {@link JsonElement} was changed, false otherwise
*/
boolean unifyOutputElement(@Nullable JsonElement jsonElement, boolean tagsToItems, String... keys);
/**
* Unifies a {@link JsonArray} as an output.<br>
* Elements treated as outputs will be converted to target items. If the element is a tag and tagsToItems is true,
* it will be converted to the target item of the tag.
* <p>
* The keys will be used for each nested element. If no keys are provided, it falls back to
* {@link RecipeConstants#DEFAULT_OUTPUT_INNER_KEYS}.
*
* @param jsonArray the {@link JsonArray} to unify
* @param tagsToItems if true, tags will be converted to target items
* @param keys the keys to use
* @return true if the {@link JsonArray} was changed, false otherwise
*/
boolean unifyOutputArray(JsonArray jsonArray, boolean tagsToItems, String... keys);
/**
* Unifies a {@link JsonObject} as an output.<br>
* Elements treated as outputs will be converted to target items. If the element is a tag and tagsToItems is true,
* it will be converted to the target item of the tag.
* <p>
* The keys will be used for each nested element. If no keys are provided, it falls back to
* {@link RecipeConstants#DEFAULT_OUTPUT_INNER_KEYS}.
*
* @param jsonObject the {@link JsonObject} to unify
* @param tagsToItems if true, tags will be converted to target items
* @param keys the keys to use
* @return true if the {@link JsonObject} was changed, false otherwise
*/
boolean unifyOutputObject(JsonObject jsonObject, boolean tagsToItems, String... keys);
/**
* Unifies a {@link JsonObject} as a tag output.<br>
* If tagsToItems is true, it will be converted to the target item of the tag. If tagsToItems is false, it
* will only be changed if the tag has an associated {@link TagSubstitutions} entry.
*
* @param jsonObject the {@link JsonObject} to unify
* @param tagsToItems if true, the tag will be converted to the target item
* @return true if the tag was changed, false otherwise
*/
boolean unifyOutputTag(JsonObject jsonObject, boolean tagsToItems);
/**
* Unifies a {@link JsonObject} as an item output.<br>
* The item will be converted to the target item of the tag if possible.
* <p>
* This uses the default keys {@link RecipeConstants#ITEM} and {@link RecipeConstants#ID}.
*
* @param jsonObject the {@link JsonObject} to unify
* @return true if the item output was changed, false otherwise
*/
boolean unifyOutputItem(JsonObject jsonObject);
/**
* Unifies a {@link JsonObject} as an item output.<br>
* The item will be converted to the target item of the tag if possible.
*
* @param jsonObject the {@link JsonObject} to unify
* @param key the key of the output entry to unify
* @return true if the item output was changed, false otherwise
*/
boolean unifyOutputItem(JsonObject jsonObject, String key);
/**
* Handles the output item replacement.
* <p>
* It needs to be ensured that the passed {@link JsonPrimitive} is an item.
*
* @param jsonPrimitive the {@link JsonPrimitive} to handle
* @return the replacement {@link JsonPrimitive} or null if no replacement was found
*/
@Nullable
JsonPrimitive handleOutputItemReplacement(JsonPrimitive jsonPrimitive);
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.api.unification.recipe;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -1,14 +0,0 @@
package com.almostreliable.unified.compat;
import com.almostreliable.unified.api.recipe.RecipeConstants;
import com.almostreliable.unified.api.recipe.RecipeUnifier;
import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder;
public class AdAstraRecipeUnifier implements RecipeUnifier {
@Override
public void collectUnifier(RecipeUnifierBuilder builder) {
builder.put("output", (json, ctx) -> {
return ctx.createResultReplacement(json, false, RecipeConstants.ITEM, "id");
});
}
}

View file

@ -1,89 +0,0 @@
package com.almostreliable.unified.compat;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.config.UnifyConfig;
import com.almostreliable.unified.utils.UnifyTag;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.Set;
import java.util.stream.Collectors;
@SuppressWarnings("unused")
public final class AlmostKube {
private AlmostKube() {}
@Nullable
public static String getPreferredTagForItem(ItemStack stack) {
UnifyTag<Item> tag = AlmostUnified
.getRuntime()
.getReplacementMap()
.orElseThrow(AlmostKube::notLoadedException)
.getPreferredTagForItem(getId(stack));
return tag == null ? null : tag.location().toString();
}
public static ItemStack getReplacementForItem(ItemStack stack) {
ResourceLocation replacement = AlmostUnified
.getRuntime()
.getReplacementMap()
.orElseThrow(AlmostKube::notLoadedException)
.getReplacementForItem(getId(stack));
return BuiltInRegistries.ITEM.get(replacement).getDefaultInstance();
}
public static ItemStack getPreferredItemForTag(ResourceLocation tag) {
UnifyTag<Item> asUnifyTag = UnifyTag.item(tag);
ResourceLocation item = AlmostUnified
.getRuntime()
.getReplacementMap()
.orElseThrow(AlmostKube::notLoadedException)
.getPreferredItemForTag(asUnifyTag, $ -> true);
return BuiltInRegistries.ITEM.get(item).getDefaultInstance();
}
public static Set<String> getTags() {
return AlmostUnified
.getRuntime()
.getFilteredTagMap()
.orElseThrow(AlmostKube::notLoadedException)
.getTags()
.stream()
.map(tag -> tag.location().toString())
.collect(Collectors.toSet());
}
public static Set<String> getItemIds(ResourceLocation tag) {
UnifyTag<Item> asUnifyTag = UnifyTag.item(tag);
return AlmostUnified
.getRuntime()
.getFilteredTagMap()
.orElseThrow(AlmostKube::notLoadedException)
.getEntriesByTag(asUnifyTag)
.stream()
.map(ResourceLocation::toString)
.collect(Collectors.toSet());
}
public static UnifyConfig getUnifyConfig() {
return AlmostUnified.getRuntime().getUnifyConfig().orElseThrow(AlmostKube::notLoadedException);
}
private static ResourceLocation getId(ItemStack stack) {
return BuiltInRegistries.ITEM
.getResourceKey(stack.getItem())
.map(ResourceKey::location)
.orElseThrow(() -> new IllegalArgumentException("Item not found in registry"));
}
private static IllegalStateException notLoadedException() {
return new IllegalStateException(
"AlmostUnifiedRuntime is unavailable in KubeJS! Possible reasons: calling runtime too early, not in a server environment"
);
}
}

View file

@ -1,74 +0,0 @@
package com.almostreliable.unified.compat;
import com.almostreliable.unified.api.recipe.RecipeConstants;
import com.almostreliable.unified.api.recipe.RecipeContext;
import com.almostreliable.unified.api.recipe.RecipeUnifier;
import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import javax.annotation.Nullable;
import java.util.List;
import java.util.function.Function;
public class GregTechModernRecipeUnifier implements RecipeUnifier {
private static final String CONTENT = "content";
@Override
public void collectUnifier(RecipeUnifierBuilder builder) {
List.of(
RecipeConstants.INPUTS,
RecipeConstants.TICK_INPUTS
).forEach(key ->
builder.put(key, (json, ctx) -> createContentReplacement(json, ctx, ctx::createIngredientReplacement))
);
List.of(
RecipeConstants.OUTPUTS,
RecipeConstants.TICK_OUTPUTS
).forEach(key ->
builder.put(
key,
(json, ctx) -> createContentReplacement(
json,
ctx,
element -> ctx.createResultReplacement(
element,
true,
RecipeConstants.ITEM,
RecipeConstants.INGREDIENT
)
)
)
);
}
@Nullable
private JsonElement createContentReplacement(@Nullable JsonElement json, RecipeContext ctx, Function<JsonElement, JsonElement> elementTransformer) {
if (json instanceof JsonObject jsonObject &&
jsonObject.get(RecipeConstants.ITEM) instanceof JsonArray jsonArray) {
JsonArray result = new JsonArray();
boolean changed = false;
for (JsonElement element : jsonArray) {
if (element instanceof JsonObject elementObject) {
JsonElement replacement = elementTransformer.apply(elementObject.get(CONTENT));
if (replacement != null) {
elementObject.add(CONTENT, replacement);
changed = true;
}
result.add(elementObject);
}
}
if (changed) {
jsonObject.add(RecipeConstants.ITEM, result);
return jsonObject;
}
}
return null;
}
}

View file

@ -1,125 +0,0 @@
package com.almostreliable.unified.compat;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.AlmostUnifiedRuntime;
import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagOwnerships;
import com.almostreliable.unified.utils.Utils;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
public final class HideHelper {
private HideHelper() {}
public static Collection<ItemStack> createHidingList(AlmostUnifiedRuntime runtime) {
ReplacementMap repMap = runtime.getReplacementMap().orElse(null);
var tagMap = runtime.getFilteredTagMap().orElse(null);
if (repMap == null || tagMap == null) return new ArrayList<>();
Set<ResourceLocation> hidingList = new HashSet<>();
for (var unifyTag : tagMap.getTags()) {
var itemsByTag = tagMap.getEntriesByTag(unifyTag);
// avoid handling single entries and tags that only contain the same namespace for all items
if (Utils.allSameNamespace(itemsByTag)) continue;
Set<ResourceLocation> replacements = new HashSet<>();
for (ResourceLocation item : itemsByTag) {
replacements.add(getReplacementForItem(repMap, item));
}
Set<ResourceLocation> toHide = new HashSet<>();
for (ResourceLocation item : itemsByTag) {
if (!replacements.contains(item)) {
toHide.add(item);
}
}
if (toHide.isEmpty()) continue;
AlmostUnified.LOG.info(
"[AutoHiding] Hiding {}/{} items for tag '#{}' -> {}",
toHide.size(),
itemsByTag.size(),
unifyTag.location(),
toHide
);
hidingList.addAll(toHide);
}
hidingList.addAll(getRefItems(repMap));
return hidingList
.stream()
.flatMap(rl -> BuiltInRegistries.ITEM.getOptional(rl).stream())
.map(ItemStack::new)
.collect(Collectors.toList());
}
/**
* Returns the replacement for the given item, or the item itself if no replacement is found.
* <p>
* Returning the item itself is important for stone strata detection.
*
* @param repMap The replacement map.
* @param item The item to get the replacement for.
* @return The replacement for the given item, or the item itself if no replacement is found.
*/
private static ResourceLocation getReplacementForItem(ReplacementMap repMap, ResourceLocation item) {
var replacement = repMap.getReplacementForItem(item);
if (replacement == null) return item;
return replacement;
}
/**
* Returns a set of all items that are contained in the reference tags.
*
* @return A set of all items that are contained in the reference tags.
*/
private static Set<ResourceLocation> getRefItems(ReplacementMap repMap) {
Set<ResourceLocation> hidingList = new HashSet<>();
TagOwnerships ownerships = repMap.getTagOwnerships();
ownerships.getRefs().forEach(ref -> {
var owner = ownerships.getOwnerByTag(ref);
assert owner != null;
var dominantItem = repMap.getPreferredItemForTag(owner, $ -> true);
TagKey<Item> asTagKey = TagKey.create(Registries.ITEM, ref.location());
Set<ResourceLocation> refItems = new HashSet<>();
BuiltInRegistries.ITEM.getTagOrEmpty(asTagKey).forEach(holder -> {
ResourceLocation item = BuiltInRegistries.ITEM.getKey(holder.value());
if (item.equals(dominantItem)) return; // don't hide if the item is a dominant one
refItems.add(item);
});
if (refItems.isEmpty()) return;
AlmostUnified.LOG.info(
"[AutoHiding] Hiding reference tag '#{}' of owner tag '#{}' -> {}",
ref.location(),
owner.location(),
refItems
);
hidingList.addAll(refItems);
});
return hidingList;
}
}

View file

@ -0,0 +1,78 @@
package com.almostreliable.unified.compat;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.api.constant.ModConstants;
import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public final class PluginManager {
@Nullable private static PluginManager INSTANCE;
private final List<AlmostUnifiedPlugin> plugins;
private PluginManager(List<AlmostUnifiedPlugin> plugins) {
this.plugins = plugins;
}
@SuppressWarnings("StaticVariableUsedBeforeInitialization")
public static PluginManager instance() {
if (INSTANCE == null) {
throw new IllegalStateException("PluginManager is not initialized");
}
return INSTANCE;
}
public static void init(Collection<AlmostUnifiedPlugin> plugins) {
if (INSTANCE != null) {
throw new IllegalStateException("PluginManager is already initialized");
}
var sortedPlugins = new ArrayList<>(plugins);
sortedPlugins.sort((a, b) -> {
if (a.getPluginId().getNamespace().equals(ModConstants.ALMOST_UNIFIED)) {
return -1;
}
if (b.getPluginId().getNamespace().equals(ModConstants.ALMOST_UNIFIED)) {
return 1;
}
return a.getPluginId().compareTo(b.getPluginId());
});
String ids = sortedPlugins
.stream()
.map(AlmostUnifiedPlugin::getPluginId)
.map(ResourceLocation::toString)
.collect(Collectors.joining(", "));
AlmostUnifiedCommon.LOGGER.info("Loaded plugins: {}", ids);
INSTANCE = new PluginManager(sortedPlugins);
}
public void registerRecipeUnifiers(RecipeUnifierRegistry registry) {
forEachPlugin(plugin -> plugin.registerRecipeUnifiers(registry));
}
public void forEachPlugin(Consumer<AlmostUnifiedPlugin> consumer) {
var it = plugins.listIterator();
while (it.hasNext()) {
AlmostUnifiedPlugin plugin = it.next();
try {
consumer.accept(plugin);
} catch (Exception e) {
it.remove();
AlmostUnifiedCommon.LOGGER.error("Failed to process plugin {}, removing it.", plugin.getPluginId(), e);
}
}
}
}

View file

@ -0,0 +1,71 @@
package com.almostreliable.unified.compat.kube;
import com.almostreliable.unified.api.AlmostUnified;
import com.almostreliable.unified.api.AlmostUnifiedRuntime;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.Set;
import java.util.stream.Collectors;
@SuppressWarnings("unused")
public final class AlmostKube {
private AlmostKube() {}
private static AlmostUnifiedRuntime getRuntime() {
return AlmostUnified.INSTANCE.getRuntimeOrThrow();
}
public static Set<String> getTags() {
return getRuntime()
.getUnificationLookup()
.getTags()
.stream()
.map(tag -> tag.location().toString())
.collect(Collectors.toSet());
}
public static Set<String> getTagEntries(ResourceLocation tag) {
var tagKey = TagKey.create(Registries.ITEM, tag);
return getRuntime()
.getUnificationLookup()
.getTagEntries(tagKey)
.stream()
.map(holder -> holder.id().toString())
.collect(Collectors.toSet());
}
@Nullable
public static String getRelevantItemTag(ItemStack stack) {
var tag = getRuntime().getUnificationLookup().getRelevantItemTag(getId(stack));
return tag == null ? null : tag.location().toString();
}
public static ItemStack getVariantItemTarget(ItemStack stack) {
var entry = getRuntime().getUnificationLookup().getVariantItemTarget(getId(stack));
if (entry == null) return ItemStack.EMPTY;
return entry.value().getDefaultInstance();
}
public static ItemStack getTagTargetItem(ResourceLocation tag) {
var tagKey = TagKey.create(Registries.ITEM, tag);
var entry = getRuntime().getUnificationLookup().getTagTargetItem(tagKey);
if (entry == null) return ItemStack.EMPTY;
return entry.value().getDefaultInstance();
}
private static ResourceLocation getId(ItemStack stack) {
return BuiltInRegistries.ITEM
.getResourceKey(stack.getItem())
.map(ResourceKey::location)
.orElseThrow(() -> new IllegalArgumentException("Item not found in registry"));
}
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.compat.kube;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,51 @@
package com.almostreliable.unified.compat.unification;
import com.almostreliable.unified.api.constant.RecipeConstants;
import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier;
import com.almostreliable.unified.api.unification.recipe.RecipeJson;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifier;
import com.almostreliable.unified.api.unification.recipe.UnificationHelper;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.function.Consumer;
public class GregTechModernRecipeUnifier implements RecipeUnifier {
private static final String TICK_INPUTS = "tickInputs";
private static final String TICK_OUTPUTS = "tickOutputs";
private static final String CONTENT = "content";
@Override
public void unify(UnificationHelper helper, RecipeJson recipe) {
GenericRecipeUnifier.INSTANCE.unify(helper, recipe);
doUnify(recipe, RecipeConstants.INPUTS, helper::unifyInputElement);
doUnify(recipe, TICK_INPUTS, helper::unifyInputElement);
doUnify(recipe,
RecipeConstants.OUTPUTS,
json -> helper.unifyOutputObject(json, true, RecipeConstants.ITEM, RecipeConstants.INGREDIENT));
doUnify(recipe,
TICK_OUTPUTS,
json -> helper.unifyOutputObject(json, true, RecipeConstants.ITEM, RecipeConstants.INGREDIENT));
}
private void doUnify(RecipeJson recipe, String key, Consumer<JsonObject> callback) {
JsonElement property = recipe.getProperty(key);
if (property == null) {
return;
}
if (!(property.getAsJsonObject().get(RecipeConstants.ITEM) instanceof JsonArray arr)) {
return;
}
for (JsonElement element : arr) {
if (element.getAsJsonObject().get(CONTENT) instanceof JsonObject content) {
callback.accept(content);
}
}
}
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.compat.unification;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,57 @@
package com.almostreliable.unified.compat.viewer;
import dev.emi.emi.api.EmiEntrypoint;
import dev.emi.emi.api.EmiInitRegistry;
import dev.emi.emi.api.EmiPlugin;
import dev.emi.emi.api.EmiRegistry;
import dev.emi.emi.api.recipe.EmiRecipe;
import dev.emi.emi.api.recipe.EmiRecipeDecorator;
import dev.emi.emi.api.stack.EmiStack;
import dev.emi.emi.api.widget.WidgetHolder;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@SuppressWarnings("UnstableApiUsage")
@EmiEntrypoint
public class AlmostEMI implements EmiPlugin {
@Override
public void initialize(EmiInitRegistry registry) {
if (!BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.EMI_STRICT_TAG).iterator().hasNext()) return;
for (Holder<Item> itemHolder : BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.HIDE_TAG)) {
registry.disableStack(EmiStack.of(new ItemStack(itemHolder)));
}
}
@Override
public void register(EmiRegistry registry) {
registry.addRecipeDecorator(new IndicatorDecorator());
if (BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.EMI_STRICT_TAG).iterator().hasNext()) return;
for (Holder<Item> itemHolder : BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.HIDE_TAG)) {
registry.removeEmiStacks(EmiStack.of(new ItemStack(itemHolder)));
}
}
private static class IndicatorDecorator implements EmiRecipeDecorator {
@Override
public void decorateRecipe(EmiRecipe recipe, WidgetHolder widgets) {
var recipeId = recipe.getId();
if (recipeId == null) return;
var link = CRTLookup.getLink(recipeId);
if (link == null) return;
int pX = recipe.getDisplayWidth() - 5;
int pY = recipe.getDisplayHeight() - 3;
int size = RecipeIndicator.RENDER_SIZE - 1;
widgets.addDrawable(0, 0, 0, 0, (guiGraphics, mX, mY, delta) ->
RecipeIndicator.renderIndicator(guiGraphics, pX, pY, size));
widgets.addTooltipText(RecipeIndicator.constructTooltip(link), pX, pY, size, size);
}
}
}

View file

@ -1,11 +1,7 @@
package com.almostreliable.unified.compat;
package com.almostreliable.unified.compat.viewer;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.AlmostUnifiedFallbackRuntime;
import com.almostreliable.unified.api.ModConstants;
import com.almostreliable.unified.config.UnifyConfig;
import com.almostreliable.unified.recipe.CRTLookup;
import com.almostreliable.unified.recipe.ClientRecipeTracker.ClientRecipeLink;
import com.almostreliable.unified.api.constant.ModConstants;
import com.almostreliable.unified.compat.viewer.ClientRecipeTracker.ClientRecipeLink;
import com.almostreliable.unified.utils.Utils;
import me.shedaniel.rei.plugincompatibilities.api.REIPluginCompatIgnore;
import mezz.jei.api.IModPlugin;
@ -17,10 +13,14 @@ import mezz.jei.api.recipe.category.extensions.IRecipeCategoryDecorator;
import mezz.jei.api.registration.IAdvancedRegistration;
import mezz.jei.api.runtime.IJeiRuntime;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
@REIPluginCompatIgnore
@ -34,15 +34,11 @@ public class AlmostJEI implements IModPlugin {
@Override
public void onRuntimeAvailable(IJeiRuntime jei) {
AlmostUnifiedFallbackRuntime.getInstance().reload();
Collection<ItemStack> items = new ArrayList<>();
for (Holder<Item> itemHolder : BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.HIDE_TAG)) {
items.add(new ItemStack(itemHolder));
}
Boolean jeiDisabled = AlmostUnified.getRuntime()
.getUnifyConfig()
.map(UnifyConfig::reiOrJeiDisabled)
.orElse(false);
if (jeiDisabled) return;
Collection<ItemStack> items = HideHelper.createHidingList(AlmostUnified.getRuntime());
if (!items.isEmpty()) {
jei.getIngredientManager().removeIngredientsAtRuntime(VanillaTypes.ITEM_STACK, items);
}

View file

@ -1,12 +1,8 @@
package com.almostreliable.unified.compat;
package com.almostreliable.unified.compat.viewer;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.AlmostUnifiedFallbackRuntime;
import com.almostreliable.unified.ClientTagUpdateEvent;
import com.almostreliable.unified.api.ModConstants;
import com.almostreliable.unified.config.UnifyConfig;
import com.almostreliable.unified.recipe.CRTLookup;
import com.almostreliable.unified.recipe.ClientRecipeTracker.ClientRecipeLink;
import com.almostreliable.unified.api.constant.ModConstants;
import com.almostreliable.unified.compat.viewer.ClientRecipeTracker.ClientRecipeLink;
import com.almostreliable.unified.utils.ClientTagUpdateEvent;
import com.almostreliable.unified.utils.Utils;
import me.shedaniel.math.Rectangle;
import me.shedaniel.rei.api.client.entry.filtering.base.BasicFilteringRule;
@ -23,8 +19,14 @@ import me.shedaniel.rei.api.common.display.Display;
import me.shedaniel.rei.api.common.plugins.PluginManager;
import me.shedaniel.rei.api.common.registry.ReloadStage;
import me.shedaniel.rei.api.common.util.EntryIngredients;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@SuppressWarnings("UnstableApiUsage")
@ -46,16 +48,12 @@ public class AlmostREI implements REIClientPlugin {
@Override
public void registerBasicEntryFiltering(BasicFilteringRule<?> rule) {
filterUpdate = rule.hide(() -> {
AlmostUnifiedFallbackRuntime.getInstance().reload();
Collection<ItemStack> items = new ArrayList<>();
for (Holder<Item> itemHolder : BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.HIDE_TAG)) {
items.add(new ItemStack(itemHolder));
}
var reiDisabled = AlmostUnified
.getRuntime()
.getUnifyConfig()
.map(UnifyConfig::reiOrJeiDisabled)
.orElse(false);
if (reiDisabled) return List.of();
return EntryIngredients.ofItemStacks(HideHelper.createHidingList(AlmostUnified.getRuntime()));
return EntryIngredients.ofItemStacks(items);
});
}

View file

@ -1,8 +1,9 @@
package com.almostreliable.unified.recipe;
package com.almostreliable.unified.compat.viewer;
import com.almostreliable.unified.BuildConfig;
import com.almostreliable.unified.utils.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.RecipeHolder;
import javax.annotation.Nullable;
@ -12,14 +13,15 @@ public final class CRTLookup {
@Nullable
public static ClientRecipeTracker.ClientRecipeLink getLink(ResourceLocation recipeId) {
ResourceLocation linkRecipe = new ResourceLocation(BuildConfig.MOD_ID, recipeId.getNamespace());
ResourceLocation link = Utils.getRL(recipeId.getNamespace());
if (Minecraft.getInstance().level == null) {
return null;
}
return Minecraft.getInstance().level
.getRecipeManager()
.byKey(linkRecipe)
.byKey(link)
.map(RecipeHolder::value)
.filter(ClientRecipeTracker.class::isInstance)
.map(ClientRecipeTracker.class::cast)
.map(tracker -> tracker.getLink(recipeId))

View file

@ -1,29 +1,36 @@
package com.almostreliable.unified.recipe;
package com.almostreliable.unified.compat.viewer;
import com.almostreliable.unified.BuildConfig;
import com.almostreliable.unified.unification.recipe.RecipeLink;
import com.almostreliable.unified.utils.Utils;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.FriendlyByteBuf;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.HolderLookup;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeInput;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This recipe is used to track which recipes were unified. It is NOT used for crafting.
* Each tracker will hold one namespace with a list of recipes that were unified for it.
*/
public class ClientRecipeTracker implements Recipe<Container> {
public record ClientRecipeTracker(String namespace, Map<ResourceLocation, ClientRecipeLink> recipes)
implements Recipe<RecipeInput> {
public static final ResourceLocation ID = Utils.getRL("client_recipe_tracker");
public static final String RECIPES = "recipes";
public static final String NAMESPACE = "namespace";
@ -37,14 +44,6 @@ public class ClientRecipeTracker implements Recipe<Container> {
}
};
private final ResourceLocation id;
private final Map<ResourceLocation, ClientRecipeLink> recipes = new HashMap<>();
private final String namespace;
protected ClientRecipeTracker(ResourceLocation id, String namespace) {
this.id = id;
this.namespace = namespace;
}
/**
* Creates a raw string representation.
@ -63,12 +62,12 @@ public class ClientRecipeTracker implements Recipe<Container> {
//<editor-fold defaultstate="collapsed" desc="Default recipe stuff. Ignore this. Forget this.">
@Override
public boolean matches(Container container, Level level) {
public boolean matches(RecipeInput recipeInput, Level level) {
return false;
}
@Override
public ItemStack assemble(Container container, RegistryAccess registryAccess) {
public ItemStack assemble(RecipeInput recipeInput, HolderLookup.Provider provider) {
return ItemStack.EMPTY;
}
@ -78,14 +77,9 @@ public class ClientRecipeTracker implements Recipe<Container> {
}
@Override
public ItemStack getResultItem(RegistryAccess registryAccess) {
public ItemStack getResultItem(HolderLookup.Provider provider) {
return ItemStack.EMPTY;
}
@Override
public ResourceLocation getId() {
return id;
}
//</editor-fold>
@Override
@ -109,10 +103,15 @@ public class ClientRecipeTracker implements Recipe<Container> {
public record ClientRecipeLink(ResourceLocation id, boolean isUnified, boolean isDuplicate) {}
public List<String> getLinkStrings() {
return recipes.values().stream().map(l -> createRaw(l.isUnified, l.isDuplicate, l.id.getPath())).toList();
}
public static class Serializer implements RecipeSerializer<ClientRecipeTracker> {
/**
* Reads a recipe from a json file. Recipe will look like this:
* Codec for the recipe tracker. The recipe will look like this:
* <pre>
* {@code
* {
@ -127,35 +126,29 @@ public class ClientRecipeTracker implements Recipe<Container> {
* }
* }
* </pre>
*
* @param recipeId The id of the recipe for the tracker.
* @param json The json object.
* @return The recipe tracker.
*/
@Override
public ClientRecipeTracker fromJson(ResourceLocation recipeId, JsonObject json) {
String namespace = json.get(NAMESPACE).getAsString();
JsonArray recipes = json.get(RECIPES).getAsJsonArray();
ClientRecipeTracker tracker = new ClientRecipeTracker(recipeId, namespace);
for (JsonElement element : recipes) {
ClientRecipeLink clientRecipeLink = parseRaw(namespace, element.getAsString());
tracker.add(clientRecipeLink);
}
return tracker;
}
public static final MapCodec<ClientRecipeTracker> CODEC = RecordCodecBuilder.mapCodec(instance -> instance
.group(
Codec.STRING.fieldOf("namespace").forGetter(ClientRecipeTracker::namespace),
Codec.list(Codec.STRING).fieldOf("recipes").forGetter(ClientRecipeTracker::getLinkStrings)
)
.apply(instance, Serializer::of));
public static final StreamCodec<RegistryFriendlyByteBuf, ClientRecipeTracker> STREAM_CODEC = new StreamCodec<>() {
@Override
public ClientRecipeTracker fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) {
public ClientRecipeTracker decode(RegistryFriendlyByteBuf buffer) {
int size = buffer.readInt();
String namespace = buffer.readUtf();
ClientRecipeTracker recipe = new ClientRecipeTracker(recipeId, namespace);
ImmutableMap.Builder<ResourceLocation, ClientRecipeLink> builder = ImmutableMap.builder();
for (int i = 0; i < size; i++) {
String raw = buffer.readUtf();
ClientRecipeLink clientRecipeLink = parseRaw(namespace, raw);
recipe.add(clientRecipeLink);
builder.put(clientRecipeLink.id(), clientRecipeLink);
}
return recipe;
return new ClientRecipeTracker(namespace, builder.build());
}
/**
@ -174,7 +167,7 @@ public class ClientRecipeTracker implements Recipe<Container> {
* @param recipe The recipe to write
*/
@Override
public void toNetwork(FriendlyByteBuf buffer, ClientRecipeTracker recipe) {
public void encode(RegistryFriendlyByteBuf buffer, ClientRecipeTracker recipe) {
buffer.writeInt(recipe.recipes.size());
buffer.writeUtf(recipe.namespace);
for (ClientRecipeLink clientRecipeLink : recipe.recipes.values()) {
@ -184,6 +177,28 @@ public class ClientRecipeTracker implements Recipe<Container> {
buffer.writeUtf(raw);
}
}
};
@Override
public MapCodec<ClientRecipeTracker> codec() {
return CODEC;
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, ClientRecipeTracker> streamCodec() {
return STREAM_CODEC;
}
private static ClientRecipeTracker of(String namespace, List<String> recipes) {
ImmutableMap.Builder<ResourceLocation, ClientRecipeLink> builder = ImmutableMap.builder();
for (String recipe : recipes) {
ClientRecipeLink link = parseRaw(namespace, recipe);
builder.put(link.id(), link);
}
return new ClientRecipeTracker(namespace, builder.build());
}
/**
* Creates a {@link ClientRecipeLink} from a raw string for the given namespace.
@ -192,12 +207,16 @@ public class ClientRecipeTracker implements Recipe<Container> {
* @param raw The raw string.
* @return The client sided recipe link.
*/
private static ClientRecipeLink parseRaw(String namespace, String raw) {
public static ClientRecipeLink parseRaw(String namespace, String raw) {
String[] split = raw.split("\\$", 2);
int flag = Integer.parseInt(split[0]);
boolean isUnified = (flag & UNIFIED_FLAG) != 0;
boolean isDuplicate = (flag & DUPLICATE_FLAG) != 0;
return new ClientRecipeLink(new ResourceLocation(namespace, split[1]), isUnified, isDuplicate);
return new ClientRecipeLink(
ResourceLocation.fromNamespaceAndPath(namespace, split[1]),
isUnified,
isDuplicate
);
}
}
@ -212,10 +231,10 @@ public class ClientRecipeTracker implements Recipe<Container> {
}
/**
* Creates a map with the namespace as key and the json recipe.
* These recipes are used later in {@link Serializer#fromJson(ResourceLocation, JsonObject)}
* Creates a map with the namespace as key and the JSON recipe.
* These recipes are used later in {@link Serializer}
*
* @return The map with the namespace as key and the json recipe.
* @return The map with the namespace as key and the JSON recipe.
*/
public Map<ResourceLocation, JsonObject> compute() {
Map<ResourceLocation, JsonObject> result = new HashMap<>();
@ -224,7 +243,7 @@ public class ClientRecipeTracker implements Recipe<Container> {
json.addProperty("type", ID.toString());
json.addProperty(NAMESPACE, namespace);
json.add(RECIPES, recipes);
result.put(new ResourceLocation(BuildConfig.MOD_ID, namespace), json);
result.put(Utils.getRL(namespace), json);
});
return result;
}

View file

@ -0,0 +1,99 @@
package com.almostreliable.unified.compat.viewer;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.api.unification.UnificationEntry;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.api.unification.UnificationSettings;
import com.almostreliable.unified.utils.Utils;
import com.almostreliable.unified.utils.VanillaTagWrapper;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
public final class ItemHider {
public static final TagKey<Item> HIDE_TAG = TagKey.create(Registries.ITEM, Utils.getRL("hide"));
public static final TagKey<Item> EMI_STRICT_TAG = TagKey.create(Registries.ITEM, Utils.getRL("emi_strict"));
private ItemHider() {}
public static void applyHideTags(VanillaTagWrapper<Item> tags, Collection<UnificationSettings> handlers, boolean emiHidingStrict) {
for (var handler : handlers) {
if (handler.shouldHideVariantItems()) {
applyHideTags(tags, handler);
}
}
if (emiHidingStrict) {
tags.add(EMI_STRICT_TAG.location(), BuiltInRegistries.ITEM.wrapAsHolder(Items.DEBUG_STICK));
}
}
public static void applyHideTags(VanillaTagWrapper<Item> tags, UnificationSettings handler) {
var holdersToHide = createHidingItems(handler);
for (Holder<Item> holder : holdersToHide) {
tags.add(HIDE_TAG.location(), holder);
}
}
public static Set<Holder<Item>> createHidingItems(UnificationSettings handler) {
Set<Holder<Item>> hidings = new HashSet<>();
for (TagKey<Item> tag : handler.getTags()) {
var entriesByTag = handler.getTagEntries(tag);
// avoid handling single entries and tags that only contain the same namespace for all items
if (Utils.allSameNamespace(entriesByTag)) continue;
Set<UnificationEntry<Item>> replacements = new HashSet<>();
for (var holder : entriesByTag) {
replacements.add(getReplacementForItem(handler, holder));
}
Set<Holder<Item>> toHide = new HashSet<>();
Set<String> toHideIds = new HashSet<>();
for (var entry : entriesByTag) {
if (!replacements.contains(entry)) {
toHide.add(entry.asHolderOrThrow());
toHideIds.add(entry.id().toString());
}
}
if (toHide.isEmpty()) continue;
AlmostUnifiedCommon.LOGGER.info(
"[AutoHiding] Hiding {}/{} items for tag '#{}' -> {}",
toHide.size(),
entriesByTag.size(),
tag.location(),
toHideIds
);
hidings.addAll(toHide);
}
return hidings;
}
/**
* Returns the replacement for the given item, or the item itself if no replacement is found.
* <p>
* Returning the item itself is important for stone variant detection.
*
* @param repMap The replacement map.
* @param entry The holder to get the replacement for.
* @return The replacement for the given item, or the item itself if no replacement is found.
*/
private static UnificationEntry<Item> getReplacementForItem(UnificationLookup repMap, UnificationEntry<Item> entry) {
var replacement = repMap.getVariantItemTarget(entry);
if (replacement == null) return entry;
return replacement;
}
}

View file

@ -1,6 +1,6 @@
package com.almostreliable.unified.compat;
package com.almostreliable.unified.compat.viewer;
import com.almostreliable.unified.recipe.ClientRecipeTracker.ClientRecipeLink;
import com.almostreliable.unified.compat.viewer.ClientRecipeTracker.ClientRecipeLink;
import com.almostreliable.unified.utils.Utils;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.compat.viewer;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -1,13 +1,13 @@
package com.almostreliable.unified.config;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.api.constant.ModConstants;
import com.almostreliable.unified.utils.JsonUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@ -21,107 +21,128 @@ import java.util.stream.Collectors;
public class Config {
private static final String CONFIG_DIR_PROPERTY = ModConstants.ALMOST_UNIFIED + ".configDir";
private final String name;
Config(String name) {
this.name = name;
}
public String getName() {
return name;
}
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public static <T extends Config> T load(String name, Serializer<T> serializer) {
JsonObject json = safeLoadJson(name);
AlmostUnifiedCommon.LOGGER.info("Loading config '{}.json'.", name);
JsonObject json = JsonUtils.safeReadFromFile(buildPath(createConfigDir(), name), new JsonObject());
T config = serializer.deserialize(json);
if (serializer.isInvalid()) {
Path filePath = buildPath(createConfigDir(), name);
if (Files.exists(filePath)) {
backupConfig(name, filePath);
}
AlmostUnified.LOG.warn("Creating config: {}", name);
save(filePath, config, serializer);
save(buildPath(createConfigDir(), config.getName()), config, serializer);
}
return config;
}
private static void backupConfig(String name, Path p) {
AlmostUnified.LOG.warn("Config {} is invalid. Backing up and recreating.", name);
Path backupPath = p.resolveSibling(p.getFileName() + ".bak");
try {
Files.deleteIfExists(backupPath);
Files.move(p, backupPath);
} catch (IOException e) {
AlmostUnified.LOG.error("Could not backup config file", e);
}
static Path createConfigDir() {
Path path = AlmostUnifiedPlatform.INSTANCE.getConfigPath();
String property = System.getProperty(CONFIG_DIR_PROPERTY);
if (property != null) {
path = Path.of(property);
}
try {
Files.createDirectories(path);
} catch (IOException e) {
AlmostUnifiedCommon.LOGGER.error("Failed to create config directory.", e);
}
return path;
}
static <T extends Config> void save(Path path, T config, Serializer<T> serializer) {
if (Files.exists(path)) {
backupConfig(path);
} else {
AlmostUnifiedCommon.LOGGER.warn("Config '{}.json' not found. Creating default config.", config.getName());
}
public static <T extends Config> void save(Path p, T config, Serializer<T> serializer) {
JsonObject json = serializer.serialize(config);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonString = gson.toJson(json);
try {
Files.writeString(p,
Files.writeString(
path,
jsonString,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE);
StandardOpenOption.WRITE
);
} catch (IOException e) {
AlmostUnified.LOG.error(e);
AlmostUnifiedCommon.LOGGER.error("Failed to save config '{}'.", config.getName(), e);
}
}
private static JsonObject safeLoadJson(String file) {
Path p = createConfigDir();
try (BufferedReader reader = Files.newBufferedReader(buildPath(p, file))) {
return new Gson().fromJson(reader, JsonObject.class);
} catch (Exception ignored) {
}
return new JsonObject();
}
private static void backupConfig(Path path) {
AlmostUnifiedCommon.LOGGER.warn("Config '{}' is invalid. Backing up and recreating.", path.getFileName());
private static Path createConfigDir() {
Path p = AlmostUnifiedPlatform.INSTANCE.getConfigPath();
Path backupPath = path.resolveSibling(path.getFileName() + ".bak");
try {
Files.createDirectories(p);
Files.deleteIfExists(backupPath);
Files.move(path, backupPath);
} catch (IOException e) {
AlmostUnified.LOG.error("Failed to create config directory", e);
AlmostUnifiedCommon.LOGGER.error("Config '{}' could not be backed up.", path.getFileName(), e);
}
return p;
}
private static Path buildPath(Path p, String name) {
return p.resolve(name + ".json");
private static Path buildPath(Path path, String name) {
return path.resolve(name + ".json");
}
public abstract static class Serializer<T extends Config> {
private boolean valid = true;
protected void setInvalid() {
this.valid = false;
private boolean valid;
T deserialize(JsonObject json) {
valid = true;
return handleDeserialization(json);
}
public boolean isInvalid() {
return !valid;
}
abstract T handleDeserialization(JsonObject json);
public <V> V safeGet(Supplier<V> supplier, V defaultValue) {
abstract JsonObject serialize(T config);
<V> V safeGet(Supplier<V> supplier, V defaultValue) {
try {
return supplier.get();
} catch (Exception e) {
setInvalid();
}
return defaultValue;
}
}
protected Set<Pattern> deserializePatterns(JsonObject json, String configKey, List<String> defaultValue) {
return safeGet(() -> JsonUtils
void setInvalid() {
this.valid = false;
}
boolean isInvalid() {
return !valid;
}
Set<Pattern> deserializePatterns(JsonObject json, String configKey, List<String> defaultValue) {
return safeGet(
() -> JsonUtils
.toList(json.getAsJsonArray(configKey))
.stream()
.map(Pattern::compile)
.collect(Collectors.toSet()),
new HashSet<>(defaultValue.stream().map(Pattern::compile).toList()));
new HashSet<>(defaultValue.stream().map(Pattern::compile).toList())
);
}
protected void serializePatterns(JsonObject json, String configKey, Set<Pattern> patterns) {
json.add(configKey,
JsonUtils.toArray(patterns
.stream()
.map(Pattern::pattern)
.toList()));
}
public abstract T deserialize(JsonObject json);
public abstract JsonObject serialize(T src);
void serializePatterns(JsonObject json, String configKey, Set<Pattern> patterns) {
json.add(configKey, JsonUtils.toArray(patterns.stream().map(Pattern::pattern).toList()));
}
}
}

View file

@ -1,96 +1,76 @@
package com.almostreliable.unified.config;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.utils.FileUtils;
import com.almostreliable.unified.utils.TagMap;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.apache.commons.lang3.StringUtils;
import java.util.Comparator;
import java.util.Map;
import java.util.stream.Collectors;
public final class DebugConfig extends Config {
public class DebugConfig extends Config {
public static final String NAME = "debug";
public static final DebugSerializer SERIALIZER = new DebugSerializer();
public final boolean dumpTagMap;
public final boolean dumpDuplicates;
public final boolean dumpUnification;
public final boolean dumpOverview;
public final boolean dumpRecipes;
private final boolean dumpDuplicates;
private final boolean dumpOverview;
private final boolean dumpRecipes;
private final boolean dumpTags;
private final boolean dumpUnification;
public DebugConfig(boolean dumpTagMap, boolean dumpDuplicates, boolean dumpUnification, boolean dumpOverview, boolean dumpRecipes) {
this.dumpTagMap = dumpTagMap;
private DebugConfig(boolean dumpDuplicates, boolean dumpOverview, boolean dumpRecipes, boolean dumpTags, boolean dumpUnification) {
super(NAME);
this.dumpDuplicates = dumpDuplicates;
this.dumpUnification = dumpUnification;
this.dumpOverview = dumpOverview;
this.dumpRecipes = dumpRecipes;
this.dumpTags = dumpTags;
this.dumpUnification = dumpUnification;
}
public void logUnifyTagDump(TagMap<Item> tagMap) {
if (!dumpTagMap) {
return;
public boolean shouldDumpDuplicates() {
return dumpDuplicates;
}
FileUtils.write(AlmostUnifiedPlatform.INSTANCE.getLogPath(), "unify_tag_dump.txt", sb -> {
sb.append(tagMap
.getTags()
.stream()
.sorted(Comparator.comparing(t -> t.location().toString()))
.map(t -> StringUtils.rightPad(t.location().toString(), 40) + " => " + tagMap
.getEntriesByTag(t)
.stream()
.map(ResourceLocation::toString)
.sorted()
.collect(Collectors.joining(", ")) + "\n")
.collect(Collectors.joining()));
});
public boolean shouldDumpOverview() {
return dumpOverview;
}
public void logRecipes(Map<ResourceLocation, JsonElement> recipes, String filename) {
if (!dumpRecipes) {
return;
public boolean shouldDumpRecipes() {
return dumpRecipes;
}
FileUtils.write(AlmostUnifiedPlatform.INSTANCE.getLogPath(),
filename,
sb -> recipes.forEach((key, value) -> sb
.append(key.toString())
.append(" [JSON]:")
.append(value.toString())
.append("\n")));
public boolean shouldDumpTags() {
return dumpTags;
}
public static class Serializer extends Config.Serializer<DebugConfig> {
public boolean shouldDumpUnification() {
return dumpUnification;
}
public static final String DUMP_TAG_MAP = "dumpTagMap";
public static final String DUMP_DUPLICATES = "dumpDuplicates";
public static final String DUMP_UNIFICATION = "dumpUnification";
public static final String DUMP_OVERVIEW = "dumpOverview";
public static final String DUMP_RECIPES = "dumpRecipes";
public static final class DebugSerializer extends Config.Serializer<DebugConfig> {
private static final String DUMP_DUPLICATES = "dump_duplicates";
private static final String DUMP_OVERVIEW = "dump_overview";
private static final String DUMP_RECIPES = "dump_recipes";
private static final String DUMP_TAGS = "dump_tags";
private static final String DUMP_UNIFICATION = "dump_unification";
private DebugSerializer() {}
@Override
public DebugConfig deserialize(JsonObject json) {
public DebugConfig handleDeserialization(JsonObject json) {
return new DebugConfig(
safeGet(() -> json.get(DUMP_TAG_MAP).getAsBoolean(), false),
safeGet(() -> json.get(DUMP_DUPLICATES).getAsBoolean(), false),
safeGet(() -> json.get(DUMP_UNIFICATION).getAsBoolean(), false),
safeGet(() -> json.get(DUMP_OVERVIEW).getAsBoolean(), false),
safeGet(() -> json.get(DUMP_RECIPES).getAsBoolean(), false)
safeGet(() -> json.get(DUMP_RECIPES).getAsBoolean(), false),
safeGet(() -> json.get(DUMP_TAGS).getAsBoolean(), false),
safeGet(() -> json.get(DUMP_UNIFICATION).getAsBoolean(), false)
);
}
@Override
public JsonObject serialize(DebugConfig src) {
public JsonObject serialize(DebugConfig config) {
JsonObject json = new JsonObject();
json.addProperty(DUMP_TAG_MAP, src.dumpTagMap);
json.addProperty(DUMP_DUPLICATES, src.dumpDuplicates);
json.addProperty(DUMP_UNIFICATION, src.dumpUnification);
json.addProperty(DUMP_OVERVIEW, src.dumpOverview);
json.addProperty(DUMP_RECIPES, src.dumpRecipes);
json.addProperty(DUMP_DUPLICATES, config.dumpDuplicates);
json.addProperty(DUMP_OVERVIEW, config.dumpOverview);
json.addProperty(DUMP_RECIPES, config.dumpRecipes);
json.addProperty(DUMP_TAGS, config.dumpTags);
json.addProperty(DUMP_UNIFICATION, config.dumpUnification);
return json;
}
}

View file

@ -2,23 +2,34 @@ package com.almostreliable.unified.config;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.utils.JsonCompare;
import com.google.common.collect.ImmutableMap;
import net.minecraft.Util;
import net.minecraft.resources.ResourceLocation;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
@SuppressWarnings("SpellCheckingInspection")
public final class Defaults {
public static final List<String> STONE_STRATA = List.of(
private Defaults() {}
public static final List<String> STONE_VARIANTS = List.of(
"stone",
"nether",
"andesite",
"deepslate",
"granite",
"diorite",
"andesite"
"granite",
"nether"
);
public static final List<String> MATERIALS = List.of(
public static final Map<String, Collection<String>> PLACEHOLDERS = Util.make(() -> {
ImmutableMap.Builder<String, Collection<String>> builder = ImmutableMap.builder();
builder.put("material", List.of(
"aeternium",
"aluminum",
"amber",
@ -26,11 +37,8 @@ public final class Defaults {
"bitumen",
"brass",
"bronze",
"charcoal",
"chrome",
"cinnabar",
"coal",
"coal_coke",
"cobalt",
"constantan",
"copper",
@ -66,82 +74,43 @@ public final class Defaults {
"tungsten",
"uranium",
"zinc"
);
));
private Defaults() {}
return builder.build();
});
public static List<String> getModPriorities(AlmostUnifiedPlatform.Platform platform) {
return switch (platform) {
case FORGE -> List.of(
public static final List<String> MOD_PRIORITIES = Stream.of(
"minecraft",
"kubejs",
"crafttweaker",
"create",
"thermal",
"immersiveengineering",
"mekanism"
);
case FABRIC -> List.of(
"minecraft",
"kubejs",
"crafttweaker",
"create",
"mekanism",
"techreborn",
"modern_industrialization",
"indrev"
);
};
}
).filter(AlmostUnifiedPlatform.INSTANCE::isModLoaded).toList();
public static List<String> getTags(AlmostUnifiedPlatform.Platform platform) {
return switch (platform) {
case FORGE -> List.of(
"forge:nuggets/{material}",
"forge:dusts/{material}",
"forge:gears/{material}",
"forge:gems/{material}",
"forge:ingots/{material}",
"forge:raw_materials/{material}",
"forge:ores/{material}",
"forge:plates/{material}",
"forge:rods/{material}",
"forge:wires/{material}",
"forge:storage_blocks/{material}",
"forge:storage_blocks/raw_{material}"
public static final List<String> TAGS = List.of(
"c:dusts/{material}",
"c:gears/{material}",
"c:gems/{material}",
"c:ingots/{material}",
"c:nuggets/{material}",
"c:ores/{material}",
"c:plates/{material}",
"c:raw_materials/{material}",
"c:rods/{material}",
"c:storage_blocks/raw_{material}",
"c:storage_blocks/{material}",
"c:wires/{material}"
);
case FABRIC -> List.of(
"c:{material}_nuggets",
"c:{material}_dusts",
"c:{material}_gears",
"c:{material}_gems",
"c:{material}_ingots",
"c:{material}_raw_materials",
"c:{material}_ores",
"c:{material}_plates",
"c:{material}_rods",
"c:{material}_blocks",
"c:{material}_wires",
"c:{material}_storage_blocks",
"c:raw_{material}_ores",
"c:raw_{material}_blocks",
"c:raw_{material}_storage_blocks"
);
};
}
public static List<String> getIgnoredRecipeTypes(AlmostUnifiedPlatform.Platform platform) {
return switch (platform) {
default -> List.of("cucumber:shaped_tag");
};
}
public static final List<String> IGNORED_RECIPE_TYPES = List.of("cucumber:shaped_tag");
public static JsonCompare.CompareSettings getDefaultDuplicateRules(AlmostUnifiedPlatform.Platform platform) {
JsonCompare.CompareSettings result = new JsonCompare.CompareSettings();
result.ignoreField(switch (platform) {
case FORGE -> "conditions";
case FABRIC -> "fabric:load_conditions";
});
result.ignoreField("group");
JsonCompare.CompareSettings result = getDefaultCompareSettings(platform);
result.addRule("cookingtime", new JsonCompare.HigherRule());
result.addRule("energy", new JsonCompare.HigherRule());
result.addRule("experience", new JsonCompare.HigherRule());
@ -149,16 +118,24 @@ public final class Defaults {
}
public static LinkedHashMap<ResourceLocation, JsonCompare.CompareSettings> getDefaultDuplicateOverrides(AlmostUnifiedPlatform.Platform platform) {
JsonCompare.CompareSettings result = getDefaultCompareSettings(platform);
result.ignoreField("pattern");
result.ignoreField("key");
LinkedHashMap<ResourceLocation, JsonCompare.CompareSettings> resultMap = new LinkedHashMap<>();
resultMap.put(ResourceLocation.withDefaultNamespace("crafting_shaped"), result);
return resultMap;
}
public static JsonCompare.CompareSettings getDefaultCompareSettings(AlmostUnifiedPlatform.Platform platform) {
JsonCompare.CompareSettings result = new JsonCompare.CompareSettings();
result.ignoreField(switch (platform) {
case FORGE -> "conditions";
case NEO_FORGE -> "neoforge:conditions";
case FABRIC -> "fabric:load_conditions";
});
result.ignoreField("group");
result.ignoreField("pattern");
result.ignoreField("key");
LinkedHashMap<ResourceLocation, JsonCompare.CompareSettings> resultMap = new LinkedHashMap<>();
resultMap.put(new ResourceLocation("minecraft", "crafting_shaped"), result);
return resultMap;
result.ignoreField("category");
result.ignoreField("show_notification");
return result;
}
}

View file

@ -1,7 +1,7 @@
package com.almostreliable.unified.config;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.recipe.RecipeLink;
import com.almostreliable.unified.unification.recipe.RecipeLink;
import com.almostreliable.unified.utils.JsonCompare;
import com.google.gson.JsonObject;
import net.minecraft.resources.ResourceLocation;
@ -10,21 +10,25 @@ import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class DuplicationConfig extends Config {
public final class DuplicateConfig extends Config {
public static final String NAME = "duplicates";
public static final DuplicateSerializer SERIALIZER = new DuplicateSerializer();
private final JsonCompare.CompareSettings defaultRules;
private final LinkedHashMap<ResourceLocation, JsonCompare.CompareSettings> overrideRules;
private final Set<Pattern> ignoreRecipeTypes;
private final Set<Pattern> ignoreRecipes;
private final Set<Pattern> ignoreRecipeIds;
private final boolean strictMode;
private final Map<ResourceLocation, Boolean> ignoredRecipeTypesCache;
public DuplicationConfig(JsonCompare.CompareSettings defaultRules, LinkedHashMap<ResourceLocation, JsonCompare.CompareSettings> overrideRules, Set<Pattern> ignoreRecipeTypes, Set<Pattern> ignoreRecipes, boolean strictMode) {
private DuplicateConfig(JsonCompare.CompareSettings defaultRules, LinkedHashMap<ResourceLocation, JsonCompare.CompareSettings> overrideRules, Set<Pattern> ignoreRecipeTypes, Set<Pattern> ignoreRecipeIds, boolean strictMode) {
super(NAME);
this.defaultRules = defaultRules;
this.overrideRules = overrideRules;
this.ignoreRecipeTypes = ignoreRecipeTypes;
this.ignoreRecipes = ignoreRecipes;
this.ignoreRecipeIds = ignoreRecipeIds;
this.strictMode = strictMode;
this.ignoredRecipeTypesCache = new HashMap<>();
}
@ -34,7 +38,7 @@ public class DuplicationConfig extends Config {
return true;
}
for (Pattern ignoreRecipePattern : ignoreRecipes) {
for (Pattern ignoreRecipePattern : ignoreRecipeIds) {
if (ignoreRecipePattern.matcher(recipe.getId().toString()).matches()) {
return true;
}
@ -72,35 +76,53 @@ public class DuplicationConfig extends Config {
ignoredRecipeTypesCache.clear();
}
public static class Serializer extends Config.Serializer<DuplicationConfig> {
public static final String DEFAULT_DUPLICATE_RULES = "defaultDuplicateRules";
public static final String OVERRIDE_DUPLICATE_RULES = "overrideDuplicateRules";
public static final String IGNORED_RECIPE_TYPES = "ignoredRecipeTypes";
public static final String IGNORED_RECIPES = "ignoredRecipes";
public static final String STRICT_MODE = "strictMode";
public static final class DuplicateSerializer extends Config.Serializer<DuplicateConfig> {
private static final String DEFAULT_DUPLICATE_RULES = "default_duplicate_rules";
private static final String OVERRIDE_DUPLICATE_RULES = "override_duplicate_rules";
private static final String IGNORED_RECIPE_TYPES = "ignored_recipe_types";
private static final String IGNORED_RECIPE_IDS = "ignored_recipe_ids";
private static final String STRICT_MODE = "strict_mode";
private DuplicateSerializer() {}
@Override
public DuplicationConfig deserialize(JsonObject json) {
public DuplicateConfig handleDeserialization(JsonObject json) {
var platform = AlmostUnifiedPlatform.INSTANCE.getPlatform();
Set<Pattern> ignoreRecipeTypes = deserializePatterns(json,
Set<Pattern> ignoreRecipeTypes = deserializePatterns(
json,
IGNORED_RECIPE_TYPES,
Defaults.getIgnoredRecipeTypes(platform));
Set<Pattern> ignoreRecipes = deserializePatterns(json, IGNORED_RECIPES, List.of());
Defaults.IGNORED_RECIPE_TYPES
);
Set<Pattern> ignoreRecipeIds = deserializePatterns(json, IGNORED_RECIPE_IDS, List.of());
JsonCompare.CompareSettings defaultRules = safeGet(() -> createCompareSet(json.getAsJsonObject(
DEFAULT_DUPLICATE_RULES)),
Defaults.getDefaultDuplicateRules(platform));
LinkedHashMap<ResourceLocation, JsonCompare.CompareSettings> overrideRules = safeGet(() -> json
LinkedHashMap<ResourceLocation, JsonCompare.CompareSettings> overrideRules = safeGet(() -> getOverrideRules(
json), Defaults.getDefaultDuplicateOverrides(platform));
boolean strictMode = safeGet(() -> json.get(STRICT_MODE).getAsBoolean(), false);
return new DuplicateConfig(
defaultRules,
overrideRules,
ignoreRecipeTypes,
ignoreRecipeIds,
strictMode
);
}
// Extracted as method because `safeGet` couldn't cast the type... Seems to be an old SDK bug :-)
// https://bugs.openjdk.org/browse/JDK-8324860
private LinkedHashMap<ResourceLocation, JsonCompare.CompareSettings> getOverrideRules(JsonObject json) {
return json
.getAsJsonObject(OVERRIDE_DUPLICATE_RULES)
.entrySet()
.stream()
.collect(Collectors.toMap(entry -> new ResourceLocation(entry.getKey()),
.collect(Collectors.toMap(entry -> ResourceLocation.parse(entry.getKey()),
entry -> createCompareSet(entry.getValue().getAsJsonObject()),
(a, b) -> b,
LinkedHashMap::new)), Defaults.getDefaultDuplicateOverrides(platform));
boolean strictMode = safeGet(() -> json.get(STRICT_MODE).getAsBoolean(), false);
return new DuplicationConfig(defaultRules, overrideRules, ignoreRecipeTypes, ignoreRecipes, strictMode);
LinkedHashMap::new));
}
private JsonCompare.CompareSettings createCompareSet(JsonObject rules) {
@ -110,16 +132,15 @@ public class DuplicationConfig extends Config {
}
@Override
public JsonObject serialize(DuplicationConfig config) {
public JsonObject serialize(DuplicateConfig config) {
JsonObject json = new JsonObject();
serializePatterns(json, IGNORED_RECIPE_TYPES, config.ignoreRecipeTypes);
serializePatterns(json, IGNORED_RECIPES, config.ignoreRecipes);
serializePatterns(json, IGNORED_RECIPE_IDS, config.ignoreRecipeIds);
json.add(DEFAULT_DUPLICATE_RULES, config.defaultRules.serialize());
JsonObject overrides = new JsonObject();
config.overrideRules.forEach((rl, compareSettings) -> {
overrides.add(rl.toString(), compareSettings.serialize());
});
config.overrideRules.forEach((rl, compareSettings) ->
overrides.add(rl.toString(), compareSettings.serialize()));
json.add(OVERRIDE_DUPLICATE_RULES, overrides);
json.addProperty(STRICT_MODE, false);

View file

@ -0,0 +1,103 @@
package com.almostreliable.unified.config;
import com.almostreliable.unified.api.unification.Placeholders;
import com.almostreliable.unified.utils.JsonUtils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
public final class PlaceholderConfig extends Config implements Placeholders {
public static final String NAME = "placeholders";
public static final PlaceholderSerializer SERIALIZER = new PlaceholderSerializer();
private final Map<String, Collection<String>> placeholders;
private PlaceholderConfig(Map<String, Collection<String>> placeholders) {
super(NAME);
this.placeholders = placeholders;
}
@Override
public Collection<String> apply(String str) {
AtomicReference<Collection<String>> inflated = new AtomicReference<>(new HashSet<>());
inflated.get().add(str);
forEach((placeholder, replacements) -> inflated.set(inflate(inflated.get(), placeholder, replacements)));
return inflated.get();
}
@Override
public Collection<String> getPlaceholders() {
return Collections.unmodifiableCollection(placeholders.keySet());
}
@Override
public Collection<String> getReplacements(String placeholder) {
return placeholders.getOrDefault(placeholder, Collections.emptyList());
}
@Override
public void forEach(BiConsumer<String, Collection<String>> consumer) {
placeholders.forEach(consumer);
}
private static Collection<String> inflate(Collection<String> values, String placeholder, Collection<String> replacements) {
String formattedPlaceholder = "{" + placeholder + "}";
Set<String> result = new HashSet<>();
for (String value : values) {
for (String replacement : replacements) {
result.add(value.replace(formattedPlaceholder, replacement));
}
}
return result;
}
public static final class PlaceholderSerializer extends Config.Serializer<PlaceholderConfig> {
private PlaceholderSerializer() {}
@Override
public PlaceholderConfig handleDeserialization(JsonObject json) {
// noinspection SizeReplaceableByIsEmpty
if (json.size() == 0) { // json.isEmpty crashes in prod...
setInvalid();
return new PlaceholderConfig(Defaults.PLACEHOLDERS);
}
Map<String, Collection<String>> replacements = safeGet(() -> {
ImmutableMap.Builder<String, Collection<String>> builder = ImmutableMap.builder();
for (var entry : json.entrySet()) {
ImmutableSet.Builder<String> placeholders = ImmutableSet.builder();
for (JsonElement element : entry.getValue().getAsJsonArray()) {
if (element.isJsonPrimitive()) {
placeholders.add(element.getAsString().trim());
}
}
builder.put(entry.getKey().trim(), placeholders.build());
}
return builder.build();
}, Defaults.PLACEHOLDERS);
return new PlaceholderConfig(replacements);
}
@Override
public JsonObject serialize(PlaceholderConfig config) {
JsonObject json = new JsonObject();
for (var entry : config.placeholders.entrySet()) {
json.add(entry.getKey(), JsonUtils.toArray(entry.getValue()));
}
return json;
}
}
}

View file

@ -1,51 +0,0 @@
package com.almostreliable.unified.config;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.utils.FileUtils;
import java.nio.file.Files;
import java.nio.file.Path;
public class ServerConfigs {
private final UnifyConfig unifyConfig;
private final DuplicationConfig dupConfig;
private final DebugConfig debugConfig;
public static ServerConfigs load() {
createGitIgnoreIfNotExists();
UnifyConfig unifyConfig = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer());
DuplicationConfig dupConfig = Config.load(DuplicationConfig.NAME, new DuplicationConfig.Serializer());
DebugConfig debugConfig = Config.load(DebugConfig.NAME, new DebugConfig.Serializer());
return new ServerConfigs(unifyConfig, dupConfig, debugConfig);
}
private ServerConfigs(UnifyConfig unifyConfig, DuplicationConfig dupConfig, DebugConfig debugConfig) {
this.unifyConfig = unifyConfig;
this.dupConfig = dupConfig;
this.debugConfig = debugConfig;
}
private static void createGitIgnoreIfNotExists() {
Path path = AlmostUnifiedPlatform.INSTANCE.getConfigPath();
if (!(Files.exists(path) && Files.isDirectory(path))) {
FileUtils.write(
AlmostUnifiedPlatform.INSTANCE.getConfigPath(),
".gitignore",
sb -> sb.append(DebugConfig.NAME).append(".json").append("\n")
);
}
}
public UnifyConfig getUnifyConfig() {
return unifyConfig;
}
public DuplicationConfig getDupConfig() {
return dupConfig;
}
public DebugConfig getDebugConfig() {
return debugConfig;
}
}

View file

@ -1,32 +1,55 @@
package com.almostreliable.unified.config;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.google.gson.JsonObject;
public class StartupConfig extends Config {
public final class StartupConfig extends Config {
public static final String NAME = "startup";
private final boolean serverOnly;
public static final StartupSerializer SERIALIZER = new StartupSerializer();
public StartupConfig(boolean serverOnly) {
private final boolean serverOnly;
private final Boolean worldGenUnification;
private StartupConfig(boolean serverOnly, boolean worldGenUnification) {
super(NAME);
this.serverOnly = serverOnly;
this.worldGenUnification = worldGenUnification;
}
public boolean isServerOnly() {
return serverOnly;
}
public static class Serializer extends Config.Serializer<StartupConfig> {
public static final String SERVER_ONLY = "serverOnly";
public boolean allowWorldGenUnification() {
return worldGenUnification;
}
public static final class StartupSerializer extends Config.Serializer<StartupConfig> {
private static final String SERVER_ONLY = "server_only";
private static final String WORLD_GEN_UNIFICATION = "world_gen_unification";
private StartupSerializer() {}
@Override
public StartupConfig deserialize(JsonObject json) {
return new StartupConfig(safeGet(() -> json.get(SERVER_ONLY).getAsBoolean(), false));
public StartupConfig handleDeserialization(JsonObject json) {
boolean serverOnly = safeGet(() -> json.get(SERVER_ONLY).getAsBoolean(), false);
boolean worldGenUnification = switch (AlmostUnifiedPlatform.INSTANCE.getPlatform()) {
case NEO_FORGE -> safeGet(() -> json.get(WORLD_GEN_UNIFICATION).getAsBoolean(), false);
case FABRIC -> false;
};
return new StartupConfig(serverOnly, worldGenUnification);
}
@Override
public JsonObject serialize(StartupConfig src) {
public JsonObject serialize(StartupConfig config) {
JsonObject json = new JsonObject();
json.addProperty(SERVER_ONLY, src.serverOnly);
json.addProperty(SERVER_ONLY, config.serverOnly);
if (AlmostUnifiedPlatform.INSTANCE.getPlatform() == AlmostUnifiedPlatform.Platform.NEO_FORGE) {
json.addProperty(WORLD_GEN_UNIFICATION, config.worldGenUnification);
}
return json;
}
}

View file

@ -0,0 +1,193 @@
package com.almostreliable.unified.config;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.api.constant.ModConstants;
import com.almostreliable.unified.unification.TagInheritance;
import com.almostreliable.unified.utils.JsonUtils;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public final class TagConfig extends Config {
public static final String NAME = "tags";
public static final TagSerializer SERIALIZER = new TagSerializer();
private final Map<ResourceLocation, Set<ResourceLocation>> customTags;
private final Map<ResourceLocation, Set<ResourceLocation>> tagSubstitutions;
private final TagInheritance.Mode itemTagInheritanceMode;
private final Map<TagKey<Item>, Set<Pattern>> itemTagInheritance;
private final TagInheritance.Mode blockTagInheritanceMode;
private final Map<TagKey<Block>, Set<Pattern>> blockTagInheritance;
private final boolean emiStrictHiding;
private TagConfig(Map<ResourceLocation, Set<ResourceLocation>> customTags, Map<ResourceLocation, Set<ResourceLocation>> tagSubstitutions, TagInheritance.Mode itemTagInheritanceMode, Map<TagKey<Item>, Set<Pattern>> itemTagInheritance, TagInheritance.Mode blockTagInheritanceMode, Map<TagKey<Block>, Set<Pattern>> blockTagInheritance, boolean emiStrictHiding) {
super(NAME);
this.customTags = customTags;
this.tagSubstitutions = tagSubstitutions;
this.itemTagInheritanceMode = itemTagInheritanceMode;
this.itemTagInheritance = itemTagInheritance;
this.blockTagInheritanceMode = blockTagInheritanceMode;
this.blockTagInheritance = blockTagInheritance;
this.emiStrictHiding = emiStrictHiding;
}
public TagInheritance getTagInheritance() {
return new TagInheritance(itemTagInheritanceMode,
itemTagInheritance,
blockTagInheritanceMode,
blockTagInheritance);
}
public Map<ResourceLocation, Set<ResourceLocation>> getCustomTags() {
return Collections.unmodifiableMap(customTags);
}
public Map<ResourceLocation, Set<ResourceLocation>> getTagSubstitutions() {
return Collections.unmodifiableMap(tagSubstitutions);
}
public boolean isEmiHidingStrict() {
return emiStrictHiding;
}
public static final class TagSerializer extends Config.Serializer<TagConfig> {
private static final String CUSTOM_TAGS = "custom_tags";
private static final String TAG_SUBSTITUTIONS = "tag_substitutions";
private static final String ITEM_TAG_INHERITANCE_MODE = "item_tag_inheritance_mode";
private static final String ITEM_TAG_INHERITANCE = "item_tag_inheritance";
private static final String BLOCK_TAG_INHERITANCE_MODE = "block_tag_inheritance_mode";
private static final String BLOCK_TAG_INHERITANCE = "block_tag_inheritance";
private static final String EMI_STRICT_HIDING = "emi_strict_hiding";
private TagSerializer() {}
@Override
public TagConfig handleDeserialization(JsonObject json) {
Map<ResourceLocation, Set<ResourceLocation>> customTags = safeGet(() -> JsonUtils.deserializeMapSet(json,
CUSTOM_TAGS,
e -> ResourceLocation.parse(e.getKey()),
ResourceLocation::parse), new HashMap<>());
Map<ResourceLocation, Set<ResourceLocation>> tagSubstitutions = safeGet(() -> JsonUtils.deserializeMapSet(
json,
TAG_SUBSTITUTIONS,
e -> ResourceLocation.parse(e.getKey()),
ResourceLocation::parse), new HashMap<>());
TagInheritance.Mode itemTagInheritanceMode = deserializeTagInheritanceMode(json,
ITEM_TAG_INHERITANCE_MODE);
Map<TagKey<Item>, Set<Pattern>> itemTagInheritance = deserializePatternsForLocations(Registries.ITEM,
json,
ITEM_TAG_INHERITANCE);
TagInheritance.Mode blockTagInheritanceMode = deserializeTagInheritanceMode(json,
BLOCK_TAG_INHERITANCE_MODE);
Map<TagKey<Block>, Set<Pattern>> blockTagInheritance = deserializePatternsForLocations(Registries.BLOCK,
json,
BLOCK_TAG_INHERITANCE);
boolean emiStrictHiding = AlmostUnifiedPlatform.INSTANCE.isModLoaded(ModConstants.EMI) ?
safeGet(() -> json.get(EMI_STRICT_HIDING).getAsBoolean(), true) :
false;
return new TagConfig(
customTags,
tagSubstitutions,
itemTagInheritanceMode,
itemTagInheritance,
blockTagInheritanceMode,
blockTagInheritance,
emiStrictHiding
);
}
@Override
public JsonObject serialize(TagConfig config) {
JsonObject json = new JsonObject();
JsonObject customTags = new JsonObject();
config.customTags.forEach((parent, child) -> {
customTags.add(parent.toString(),
JsonUtils.toArray(child.stream().map(ResourceLocation::toString).toList()));
});
json.add(CUSTOM_TAGS, customTags);
JsonObject tagSubstitutions = new JsonObject();
config.tagSubstitutions.forEach((parent, child) -> {
tagSubstitutions.add(parent.toString(),
JsonUtils.toArray(child.stream().map(ResourceLocation::toString).toList()));
});
json.add(TAG_SUBSTITUTIONS, tagSubstitutions);
JsonObject itemTagInheritance = new JsonObject();
config.itemTagInheritance.forEach((tag, patterns) -> {
itemTagInheritance.add(tag.toString(),
JsonUtils.toArray(patterns.stream().map(Pattern::toString).toList()));
});
json.add(ITEM_TAG_INHERITANCE_MODE, new JsonPrimitive(config.itemTagInheritanceMode.toString()));
json.add(ITEM_TAG_INHERITANCE, itemTagInheritance);
JsonObject blockTagInheritance = new JsonObject();
config.blockTagInheritance.forEach((tag, patterns) -> {
blockTagInheritance.add(tag.toString(),
JsonUtils.toArray(patterns.stream().map(Pattern::toString).toList()));
});
json.add(BLOCK_TAG_INHERITANCE_MODE, new JsonPrimitive(config.blockTagInheritanceMode.toString()));
json.add(BLOCK_TAG_INHERITANCE, blockTagInheritance);
if (AlmostUnifiedPlatform.INSTANCE.isModLoaded(ModConstants.EMI)) {
json.addProperty(EMI_STRICT_HIDING, config.emiStrictHiding);
}
return json;
}
/**
* Deserializes a list of patterns from a json object with a base key. Example json:
* <pre>
* {
* "baseKey": {
* "location1": [ pattern1, pattern2 ],
* "location2": [ pattern3, pattern4 ]
* }
* }
* </pre>
*
* @param rawConfigJson The raw config json
* @param baseKey The base key
* @return The deserialized patterns separated by location
*/
private <T> Map<TagKey<T>, Set<Pattern>> unsafeDeserializePatternsForLocations(ResourceKey<Registry<T>> registry, JsonObject rawConfigJson, String baseKey) {
return JsonUtils.deserializeMapSet(rawConfigJson,
baseKey,
e -> TagKey.create(registry, ResourceLocation.parse(e.getKey())),
Pattern::compile);
}
private <T> Map<TagKey<T>, Set<Pattern>> deserializePatternsForLocations(ResourceKey<Registry<T>> registry, JsonObject rawConfigJson, String baseKey) {
return safeGet(() -> unsafeDeserializePatternsForLocations(registry, rawConfigJson, baseKey),
new HashMap<>());
}
private TagInheritance.Mode deserializeTagInheritanceMode(JsonObject json, String key) {
return safeGet(() -> TagInheritance.Mode.valueOf(json
.getAsJsonPrimitive(key)
.getAsString()
.toUpperCase()), TagInheritance.Mode.ALLOW);
}
}
}

View file

@ -0,0 +1,354 @@
package com.almostreliable.unified.config;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.api.unification.ModPriorities;
import com.almostreliable.unified.api.unification.Placeholders;
import com.almostreliable.unified.unification.ModPrioritiesImpl;
import com.almostreliable.unified.utils.JsonUtils;
import com.google.gson.JsonObject;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public final class UnificationConfig extends Config {
private static final String SUB_FOLDER = "unification";
private final List<String> modPriorities;
private final Map<TagKey<Item>, String> priorityOverrides;
private final List<String> stoneVariants;
private final List<String> tags;
private final Set<TagKey<Item>> ignoredTags;
private final Set<Pattern> ignoredItems;
private final Set<Pattern> ignoredRecipeTypes;
private final Set<Pattern> ignoredRecipeIds;
private final boolean recipeViewerHiding;
private final boolean lootUnification;
private final Set<Pattern> ignoredLootTables;
private final Map<ResourceLocation, Boolean> ignoredItemsCache = new HashMap<>();
private final Map<ResourceLocation, Boolean> ignoredRecipeTypesCache = new HashMap<>();
private final Map<ResourceLocation, Boolean> ignoredRecipeIdsCache = new HashMap<>();
private final Map<ResourceLocation, Boolean> ignoredLootTablesCache = new HashMap<>();
@Nullable private Set<TagKey<Item>> bakedTags;
private UnificationConfig(String name, List<String> modPriorities, Map<TagKey<Item>, String> priorityOverrides, List<String> stoneVariants, List<String> tags, Set<TagKey<Item>> ignoredTags, Set<Pattern> ignoredItems, Set<Pattern> ignoredRecipeTypes, Set<Pattern> ignoredRecipeIds, boolean recipeViewerHiding, boolean lootUnification, Set<Pattern> ignoredLootTables) {
super(name);
this.modPriorities = modPriorities;
this.priorityOverrides = priorityOverrides;
this.stoneVariants = stoneVariants;
this.tags = tags;
this.ignoredTags = ignoredTags;
this.ignoredItems = ignoredItems;
this.ignoredRecipeTypes = ignoredRecipeTypes;
this.ignoredRecipeIds = ignoredRecipeIds;
this.recipeViewerHiding = recipeViewerHiding;
this.lootUnification = lootUnification;
this.ignoredLootTables = ignoredLootTables;
}
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public static Collection<UnificationConfig> safeLoadConfigs() {
try {
return loadConfigs();
} catch (Exception e) {
AlmostUnifiedCommon.LOGGER.error("Could not load unify configs.", e);
return List.of();
}
}
private static Collection<UnificationConfig> loadConfigs() throws IOException {
Path subFolder = createConfigDir().resolve(SUB_FOLDER);
var jsons = readJsons(subFolder);
if (jsons.isEmpty()) {
String name = "materials";
UnifySerializer serializer = new UnifySerializer(name);
UnificationConfig defaultConfig = serializer.deserialize(new JsonObject());
save(subFolder.resolve(name + ".json"), defaultConfig, serializer);
return List.of(defaultConfig);
}
Collection<UnificationConfig> configs = new ArrayList<>();
for (var entry : jsons.entrySet()) {
String name = entry.getKey();
JsonObject json = entry.getValue();
UnifySerializer serializer = new UnifySerializer(name);
var config = serializer.deserialize(json);
if (serializer.isInvalid()) {
save(subFolder.resolve(name + ".json"), config, serializer);
}
configs.add(config);
}
logMissingPriorityMods(configs);
return configs;
}
private static Map<String, JsonObject> readJsons(Path subFolder) throws IOException {
Files.createDirectories(subFolder);
var files = FileUtils.listFiles(subFolder.toFile(), new String[]{ "json" }, false);
Map<String, JsonObject> jsons = new HashMap<>();
for (var file : files) {
String fileName = FilenameUtils.getBaseName(file.getName());
try {
jsons.put(fileName, JsonUtils.readFromFile(file.toPath(), JsonObject.class));
} catch (Throwable e) {
AlmostUnifiedCommon.LOGGER.error("Unify config '{}.json' could not be loaded.", fileName, e);
}
}
return jsons;
}
private static void logMissingPriorityMods(Collection<UnificationConfig> unificationConfigs) {
Set<String> mods = unificationConfigs
.stream()
.map(UnificationConfig::getModPriorities)
.flatMap(ModPriorities::stream)
.filter(m -> !AlmostUnifiedPlatform.INSTANCE.isModLoaded(m))
.collect(Collectors.toSet());
if (mods.isEmpty()) return;
AlmostUnifiedCommon.LOGGER.warn(
"The following mods are used in unification settings, but are not loaded: {}",
mods
);
}
public ModPriorities getModPriorities() {
return new ModPrioritiesImpl(modPriorities, priorityOverrides);
}
public List<String> getStoneVariants() {
return stoneVariants;
}
public Set<TagKey<Item>> getTags() {
if (bakedTags == null) {
throw new IllegalStateException("unification tags are not baked yet");
}
return bakedTags;
}
public Set<TagKey<Item>> bakeTags(Predicate<TagKey<Item>> tagValidator, Placeholders placeholders) {
if (bakedTags != null) return bakedTags;
bakedTags = new HashSet<>();
for (var tag : tags) {
var replacedTags = placeholders.apply(tag);
for (var replacedTag : replacedTags) {
ResourceLocation parsedTag = ResourceLocation.tryParse(replacedTag);
if (parsedTag == null) continue;
var tagKey = TagKey.create(Registries.ITEM, parsedTag);
if (ignoredTags.contains(tagKey)) continue;
if (!tagValidator.test(tagKey)) continue;
bakedTags.add(tagKey);
}
}
return bakedTags;
}
public boolean shouldIncludeItem(ResourceLocation item) {
return ignoredItemsCache.computeIfAbsent(item, i -> {
for (Pattern pattern : ignoredItems) {
if (pattern.matcher(i.toString()).matches()) {
return false;
}
}
return true;
});
}
public boolean shouldIncludeRecipeType(ResourceLocation type) {
return ignoredRecipeTypesCache.computeIfAbsent(type, t -> {
for (Pattern pattern : ignoredRecipeTypes) {
if (pattern.matcher(t.toString()).matches()) {
return false;
}
}
return true;
});
}
public boolean shouldIncludeRecipeId(ResourceLocation id) {
return ignoredRecipeIdsCache.computeIfAbsent(id, i -> {
for (Pattern pattern : ignoredRecipeIds) {
if (pattern.matcher(i.toString()).matches()) {
return false;
}
}
return true;
});
}
public boolean shouldHideVariantItems() {
return recipeViewerHiding;
}
public boolean shouldUnifyLoot() {
return lootUnification;
}
public boolean shouldIncludeLootTable(ResourceLocation table) {
return ignoredLootTablesCache.computeIfAbsent(table, t -> {
for (Pattern pattern : ignoredRecipeIds) {
if (pattern.matcher(t.toString()).matches()) {
return false;
}
}
return true;
});
}
public void clearCaches() {
ignoredItemsCache.clear();
ignoredRecipeTypesCache.clear();
ignoredRecipeIdsCache.clear();
ignoredLootTablesCache.clear();
}
public static final class UnifySerializer extends Config.Serializer<UnificationConfig> {
private static final String MOD_PRIORITIES = "mod_priorities";
private static final String PRIORITY_OVERRIDES = "priority_overrides";
private static final String STONE_VARIANTS = "stone_variants";
private static final String TAGS = "tags";
private static final String IGNORED_TAGS = "ignored_tags";
private static final String IGNORED_ITEMS = "ignored_items";
private static final String IGNORED_RECIPE_TYPES = "ignored_recipe_types";
private static final String IGNORED_RECIPES_IDS = "ignored_recipe_ids";
private static final String RECIPE_VIEWER_HIDING = "recipe_viewer_hiding";
private static final String LOOT_UNIFICATION = "loot_unification";
private static final String IGNORED_LOOT_TABLES = "ignored_loot_tables";
private final String name;
private UnifySerializer(String name) {
this.name = name;
}
@Override
public UnificationConfig handleDeserialization(JsonObject json) {
List<String> modPriorities = safeGet(
() -> JsonUtils.toList(json.getAsJsonArray(MOD_PRIORITIES)),
Defaults.MOD_PRIORITIES
);
Map<TagKey<Item>, String> priorityOverrides = safeGet(
() -> JsonUtils.deserializeMap(
json,
PRIORITY_OVERRIDES,
e -> TagKey.create(Registries.ITEM, ResourceLocation.parse(e.getKey())),
e -> e.getValue().getAsString()
),
new HashMap<>()
);
List<String> stoneVariants = safeGet(
() -> JsonUtils.toList(json.getAsJsonArray(STONE_VARIANTS)),
Defaults.STONE_VARIANTS
);
List<String> tags = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(TAGS)), Defaults.TAGS);
Set<TagKey<Item>> ignoredTags = safeGet(
() -> JsonUtils
.toList(json.getAsJsonArray(IGNORED_TAGS))
.stream()
.map(s -> TagKey.create(Registries.ITEM, ResourceLocation.parse(s)))
.collect(Collectors.toSet()),
new HashSet<>()
);
Set<Pattern> ignoredItems = deserializePatterns(json, IGNORED_ITEMS, List.of());
Set<Pattern> ignoredRecipeTypes = deserializePatterns(
json,
IGNORED_RECIPE_TYPES,
Defaults.IGNORED_RECIPE_TYPES
);
Set<Pattern> ignoredRecipeIds = deserializePatterns(json, IGNORED_RECIPES_IDS, List.of());
boolean recipeViewerHiding = safeGet(
() -> json.getAsJsonPrimitive(RECIPE_VIEWER_HIDING).getAsBoolean(),
true
);
boolean lootUnification = safeGet(
() -> json.getAsJsonPrimitive(LOOT_UNIFICATION).getAsBoolean(),
false
);
Set<Pattern> ignoredLootTables = deserializePatterns(json, IGNORED_LOOT_TABLES, List.of());
return new UnificationConfig(
name,
modPriorities,
priorityOverrides,
stoneVariants,
tags,
ignoredTags,
ignoredItems,
ignoredRecipeTypes,
ignoredRecipeIds,
recipeViewerHiding,
lootUnification,
ignoredLootTables
);
}
@Override
public JsonObject serialize(UnificationConfig config) {
JsonObject json = new JsonObject();
json.add(MOD_PRIORITIES, JsonUtils.toArray(config.modPriorities));
JsonObject priorityOverrides = new JsonObject();
config.priorityOverrides.forEach(
(tag, mod) -> priorityOverrides.addProperty(tag.location().toString(), mod)
);
json.add(PRIORITY_OVERRIDES, priorityOverrides);
json.add(STONE_VARIANTS, JsonUtils.toArray(config.stoneVariants));
json.add(TAGS, JsonUtils.toArray(config.tags));
json.add(
IGNORED_TAGS,
JsonUtils.toArray(config.ignoredTags
.stream()
.map(TagKey::location)
.map(ResourceLocation::toString)
.toList())
);
serializePatterns(json, IGNORED_ITEMS, config.ignoredItems);
serializePatterns(json, IGNORED_RECIPE_TYPES, config.ignoredRecipeTypes);
serializePatterns(json, IGNORED_RECIPES_IDS, config.ignoredRecipeIds);
json.addProperty(RECIPE_VIEWER_HIDING, config.recipeViewerHiding);
json.addProperty(LOOT_UNIFICATION, config.lootUnification);
serializePatterns(json, IGNORED_LOOT_TABLES, config.ignoredLootTables);
return json;
}
}
}

View file

@ -1,420 +0,0 @@
package com.almostreliable.unified.config;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.utils.JsonUtils;
import com.almostreliable.unified.utils.UnifyTag;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class UnifyConfig extends Config {
public static final String NAME = "unify";
private final List<String> modPriorities;
private final List<String> stoneStrata;
private final List<String> unbakedTags;
private final List<String> materials;
private final Map<ResourceLocation, String> priorityOverrides;
private final Map<ResourceLocation, Set<ResourceLocation>> customTags;
private final Map<ResourceLocation, Set<ResourceLocation>> tagOwnerships;
private final Enum<TagInheritanceMode> itemTagInheritanceMode;
private final Map<ResourceLocation, Set<Pattern>> itemTagInheritance;
private final Enum<TagInheritanceMode> blockTagInheritanceMode;
private final Map<ResourceLocation, Set<Pattern>> blockTagInheritance;
private final Set<UnifyTag<Item>> ignoredTags;
private final Set<Pattern> ignoredItems;
private final Set<Pattern> ignoredRecipeTypes;
private final Set<Pattern> ignoredRecipes;
private final boolean hideJeiRei;
private final Map<ResourceLocation, Boolean> ignoredRecipeTypesCache;
@Nullable private Set<UnifyTag<Item>> bakedTagsCache;
public UnifyConfig(
List<String> modPriorities,
List<String> stoneStrata,
List<String> unbakedTags,
List<String> materials,
Map<ResourceLocation, String> priorityOverrides,
Map<ResourceLocation, Set<ResourceLocation>> customTags,
Map<ResourceLocation, Set<ResourceLocation>> tagOwnerships,
Enum<TagInheritanceMode> itemTagInheritanceMode,
Map<ResourceLocation, Set<Pattern>> itemTagInheritance,
Enum<TagInheritanceMode> blockTagInheritanceMode,
Map<ResourceLocation, Set<Pattern>> blockTagInheritance,
Set<UnifyTag<Item>> ignoredTags,
Set<Pattern> ignoredItems,
Set<Pattern> ignoredRecipeTypes,
Set<Pattern> ignoredRecipes,
boolean hideJeiRei
) {
this.modPriorities = modPriorities;
this.stoneStrata = stoneStrata;
this.unbakedTags = unbakedTags;
this.materials = materials;
this.priorityOverrides = priorityOverrides;
this.customTags = customTags;
this.tagOwnerships = tagOwnerships;
this.itemTagInheritanceMode = itemTagInheritanceMode;
this.itemTagInheritance = itemTagInheritance;
this.blockTagInheritanceMode = blockTagInheritanceMode;
this.blockTagInheritance = blockTagInheritance;
this.ignoredTags = ignoredTags;
this.ignoredItems = ignoredItems;
this.ignoredRecipeTypes = ignoredRecipeTypes;
this.ignoredRecipes = ignoredRecipes;
this.hideJeiRei = hideJeiRei;
this.ignoredRecipeTypesCache = new HashMap<>();
}
public List<String> getModPriorities() {
return Collections.unmodifiableList(modPriorities);
}
public List<String> getStoneStrata() {
return Collections.unmodifiableList(stoneStrata);
}
public Set<UnifyTag<Item>> bakeTags() {
return bakeTags($ -> true);
}
public Set<UnifyTag<Item>> bakeAndValidateTags(Map<ResourceLocation, Collection<Holder<Item>>> tags) {
return bakeTags(tags::containsKey);
}
private Set<UnifyTag<Item>> bakeTags(Predicate<ResourceLocation> tagValidator) {
if (bakedTagsCache != null) {
return bakedTagsCache;
}
Set<UnifyTag<Item>> result = new HashSet<>();
Set<UnifyTag<Item>> wrongTags = new HashSet<>();
for (String tag : unbakedTags) {
for (String material : materials) {
String replace = tag.replace("{material}", material);
ResourceLocation asRL = ResourceLocation.tryParse(replace);
if (asRL == null) {
AlmostUnified.LOG.warn("Could not bake tag <{}> with material <{}>", tag, material);
continue;
}
UnifyTag<Item> t = UnifyTag.item(asRL);
if (ignoredTags.contains(t)) continue;
if (!tagValidator.test(asRL)) {
wrongTags.add(t);
continue;
}
result.add(t);
}
}
if (!wrongTags.isEmpty()) {
AlmostUnified.LOG.warn(
"The following tags are invalid and will be ignored: {}",
wrongTags.stream().map(UnifyTag::location).collect(Collectors.toList())
);
}
bakedTagsCache = result;
return result;
}
// exposed for KubeJS binding
@SuppressWarnings("unused")
public List<String> getMaterials() {
return Collections.unmodifiableList(materials);
}
public Map<ResourceLocation, String> getPriorityOverrides() {
return Collections.unmodifiableMap(priorityOverrides);
}
public Map<ResourceLocation, Set<ResourceLocation>> getCustomTags() {
return Collections.unmodifiableMap(customTags);
}
public Map<ResourceLocation, Set<ResourceLocation>> getTagOwnerships() {
return Collections.unmodifiableMap(tagOwnerships);
}
public boolean shouldInheritItemTag(UnifyTag<Item> itemTag, Set<UnifyTag<Item>> dominantTags) {
var patterns = itemTagInheritance.get(itemTag.location());
boolean result = checkPatterns(dominantTags, patterns);
// noinspection SimplifiableConditionalExpression
return itemTagInheritanceMode == TagInheritanceMode.ALLOW ? result : !result;
}
public boolean shouldInheritBlockTag(UnifyTag<Block> itemTag, Set<UnifyTag<Item>> dominantTags) {
var patterns = blockTagInheritance.get(itemTag.location());
boolean result = checkPatterns(dominantTags, patterns);
// noinspection SimplifiableConditionalExpression
return blockTagInheritanceMode == TagInheritanceMode.ALLOW ? result : !result;
}
/**
* Checks all patterns against all dominant tags.
* <p>
* This implementation works based on the assumption that the mode is {@link TagInheritanceMode#ALLOW}.
* Flip the result if the mode is {@link TagInheritanceMode#DENY}.
*
* @param dominantTags The tags of the dominant item to check.
* @param patterns The patterns to check against.
* @param <T> The type of the dominant tags.
* @return Whether the dominant tags match any of the patterns.
*/
private static <T> boolean checkPatterns(Set<UnifyTag<T>> dominantTags, @Nullable Set<Pattern> patterns) {
if (patterns == null) return false;
for (var pattern : patterns) {
for (var dominantTag : dominantTags) {
if (pattern.matcher(dominantTag.location().toString()).matches()) {
return true;
}
}
}
return false;
}
public boolean includeItem(ResourceLocation item) {
for (Pattern pattern : ignoredItems) {
if (pattern.matcher(item.toString()).matches()) {
return false;
}
}
return true;
}
public boolean includeRecipe(ResourceLocation recipe) {
for (Pattern pattern : ignoredRecipes) {
if (pattern.matcher(recipe.toString()).matches()) {
return false;
}
}
return true;
}
public boolean includeRecipeType(ResourceLocation type) {
return ignoredRecipeTypesCache.computeIfAbsent(type, t -> {
for (Pattern pattern : ignoredRecipeTypes) {
if (pattern.matcher(t.toString()).matches()) {
return false;
}
}
return true;
});
}
public boolean reiOrJeiDisabled() {
return !hideJeiRei;
}
public void clearCache() {
ignoredRecipeTypesCache.clear();
}
public static class Serializer extends Config.Serializer<UnifyConfig> {
public static final String MOD_PRIORITIES = "modPriorities";
public static final String STONE_STRATA = "stoneStrata";
public static final String TAGS = "tags";
public static final String MATERIALS = "materials";
public static final String PRIORITY_OVERRIDES = "priorityOverrides";
public static final String CUSTOM_TAGS = "customTags";
public static final String TAG_OWNERSHIPS = "tagOwnerships";
public static final String ITEM_TAG_INHERITANCE_MODE = "itemTagInheritanceMode";
public static final String ITEM_TAG_INHERITANCE = "itemTagInheritance";
public static final String BLOCK_TAG_INHERITANCE_MODE = "blockTagInheritanceMode";
public static final String BLOCK_TAG_INHERITANCE = "blockTagInheritance";
public static final String IGNORED_TAGS = "ignoredTags";
public static final String IGNORED_ITEMS = "ignoredItems";
public static final String IGNORED_RECIPE_TYPES = "ignoredRecipeTypes";
public static final String IGNORED_RECIPES = "ignoredRecipes";
public static final String HIDE_JEI_REI = "itemsHidingJeiRei";
@Override
public UnifyConfig deserialize(JsonObject json) {
var platform = AlmostUnifiedPlatform.INSTANCE.getPlatform();
List<String> modPriorities = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(MOD_PRIORITIES)),
Defaults.getModPriorities(platform));
List<String> stoneStrata = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(STONE_STRATA)),
Defaults.STONE_STRATA);
List<String> tags = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(TAGS)), Defaults.getTags(platform));
List<String> materials = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(MATERIALS)),
Defaults.MATERIALS);
Map<ResourceLocation, String> priorityOverrides = safeGet(
() -> JsonUtils.deserializeMap(
json,
PRIORITY_OVERRIDES,
e -> new ResourceLocation(e.getKey()),
e -> e.getValue().getAsString()
),
new HashMap<>()
);
Map<ResourceLocation, Set<ResourceLocation>> customTags = safeGet(
() -> JsonUtils.deserializeMapSet(
json,
CUSTOM_TAGS,
e -> new ResourceLocation(e.getKey()),
ResourceLocation::new
),
new HashMap<>()
);
Map<ResourceLocation, Set<ResourceLocation>> tagOwnerships = safeGet(
() -> JsonUtils.deserializeMapSet(
json,
TAG_OWNERSHIPS,
e -> new ResourceLocation(e.getKey()),
ResourceLocation::new
),
new HashMap<>()
);
Enum<TagInheritanceMode> itemTagInheritanceMode = deserializeTagInheritanceMode(json,
ITEM_TAG_INHERITANCE_MODE);
Map<ResourceLocation, Set<Pattern>> itemTagInheritance = deserializePatternsForLocations(json,
ITEM_TAG_INHERITANCE);
Enum<TagInheritanceMode> blockTagInheritanceMode = deserializeTagInheritanceMode(json,
BLOCK_TAG_INHERITANCE_MODE);
Map<ResourceLocation, Set<Pattern>> blockTagInheritance = deserializePatternsForLocations(json,
BLOCK_TAG_INHERITANCE);
Set<UnifyTag<Item>> ignoredTags = safeGet(() -> JsonUtils
.toList(json.getAsJsonArray(IGNORED_TAGS))
.stream()
.map(s -> UnifyTag.item(new ResourceLocation(s)))
.collect(Collectors.toSet()), new HashSet<>());
Set<Pattern> ignoredItems = deserializePatterns(json, IGNORED_ITEMS, List.of());
Set<Pattern> ignoredRecipeTypes = deserializePatterns(json, IGNORED_RECIPE_TYPES,
Defaults.getIgnoredRecipeTypes(platform));
Set<Pattern> ignoredRecipes = deserializePatterns(json, IGNORED_RECIPES, List.of());
boolean hideJeiRei = safeGet(() -> json.getAsJsonPrimitive(HIDE_JEI_REI).getAsBoolean(), true);
return new UnifyConfig(
modPriorities,
stoneStrata,
tags,
materials,
priorityOverrides,
customTags,
tagOwnerships,
itemTagInheritanceMode,
itemTagInheritance,
blockTagInheritanceMode,
blockTagInheritance,
ignoredTags,
ignoredItems,
ignoredRecipeTypes,
ignoredRecipes,
hideJeiRei
);
}
private TagInheritanceMode deserializeTagInheritanceMode(JsonObject json, String key) {
return safeGet(() -> TagInheritanceMode.valueOf(json
.getAsJsonPrimitive(key)
.getAsString().toUpperCase()), TagInheritanceMode.ALLOW);
}
/**
* Deserializes a list of patterns from a json object with a base key. Example json:
* <pre>
* {
* "baseKey": {
* "location1": [ pattern1, pattern2 ],
* "location2": [ pattern3, pattern4 ]
* }
* }
* </pre>
*
* @param rawConfigJson The raw config json
* @param baseKey The base key
* @return The deserialized patterns separated by location
*/
private Map<ResourceLocation, Set<Pattern>> unsafeDeserializePatternsForLocations(JsonObject rawConfigJson, String baseKey) {
return JsonUtils.deserializeMapSet(
rawConfigJson,
baseKey,
e -> new ResourceLocation(e.getKey()),
Pattern::compile
);
}
private Map<ResourceLocation, Set<Pattern>> deserializePatternsForLocations(JsonObject rawConfigJson, String baseKey) {
return safeGet(() -> unsafeDeserializePatternsForLocations(rawConfigJson, baseKey), new HashMap<>());
}
@Override
public JsonObject serialize(UnifyConfig config) {
JsonObject json = new JsonObject();
json.add(MOD_PRIORITIES, JsonUtils.toArray(config.modPriorities));
json.add(STONE_STRATA, JsonUtils.toArray(config.stoneStrata));
json.add(TAGS, JsonUtils.toArray(config.unbakedTags));
json.add(MATERIALS, JsonUtils.toArray(config.materials));
JsonObject priorityOverrides = new JsonObject();
config.priorityOverrides.forEach((tag, mod) -> {
priorityOverrides.addProperty(tag.toString(), mod);
});
json.add(PRIORITY_OVERRIDES, priorityOverrides);
JsonObject customTags = new JsonObject();
config.customTags.forEach((parent, child) -> {
customTags.add(parent.toString(),
JsonUtils.toArray(child.stream().map(ResourceLocation::toString).toList()));
});
json.add(CUSTOM_TAGS, customTags);
JsonObject tagOwnerships = new JsonObject();
config.tagOwnerships.forEach((parent, child) -> {
tagOwnerships.add(parent.toString(),
JsonUtils.toArray(child.stream().map(ResourceLocation::toString).toList()));
});
json.add(TAG_OWNERSHIPS, tagOwnerships);
JsonObject itemTagInheritance = new JsonObject();
config.itemTagInheritance.forEach((tag, patterns) -> {
itemTagInheritance.add(tag.toString(),
JsonUtils.toArray(patterns.stream().map(Pattern::toString).toList()));
});
json.add(ITEM_TAG_INHERITANCE_MODE, new JsonPrimitive(config.itemTagInheritanceMode.toString()));
json.add(ITEM_TAG_INHERITANCE, itemTagInheritance);
JsonObject blockTagInheritance = new JsonObject();
config.blockTagInheritance.forEach((tag, patterns) -> {
blockTagInheritance.add(tag.toString(),
JsonUtils.toArray(patterns.stream().map(Pattern::toString).toList()));
});
json.add(BLOCK_TAG_INHERITANCE_MODE, new JsonPrimitive(config.blockTagInheritanceMode.toString()));
json.add(BLOCK_TAG_INHERITANCE, blockTagInheritance);
json.add(IGNORED_TAGS,
JsonUtils.toArray(config.ignoredTags
.stream()
.map(UnifyTag::location)
.map(ResourceLocation::toString)
.toList()));
serializePatterns(json, IGNORED_ITEMS, config.ignoredItems);
serializePatterns(json, IGNORED_RECIPE_TYPES, config.ignoredRecipeTypes);
serializePatterns(json, IGNORED_RECIPES, config.ignoredRecipes);
json.addProperty(HIDE_JEI_REI, config.hideJeiRei);
return json;
}
}
public enum TagInheritanceMode {
ALLOW,
DENY
}
}

View file

@ -0,0 +1,85 @@
package com.almostreliable.unified.core;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.api.AlmostUnified;
import com.almostreliable.unified.api.AlmostUnifiedRuntime;
import com.almostreliable.unified.api.unification.UnificationEntry;
import com.google.auto.service.AutoService;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.ItemLike;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
@AutoService(AlmostUnified.class)
public class AlmostUnifiedImpl implements AlmostUnified {
@Override
public boolean isRuntimeLoaded() {
return getRuntime() != null;
}
@Nullable
@Override
public AlmostUnifiedRuntime getRuntime() {
return AlmostUnifiedCommon.getRuntime();
}
@Override
public AlmostUnifiedRuntime getRuntimeOrThrow() {
AlmostUnifiedRuntime runtime = AlmostUnifiedCommon.getRuntime();
if (runtime == null) {
throw new IllegalStateException("runtime is not loaded");
}
return runtime;
}
@Override
public Collection<TagKey<Item>> getTags() {
if (!isRuntimeLoaded()) return Set.of();
return getRuntimeOrThrow().getUnificationLookup().getTags();
}
@Override
public Collection<Item> getTagEntries(TagKey<Item> tag) {
if (!isRuntimeLoaded()) return Set.of();
return getRuntimeOrThrow()
.getUnificationLookup()
.getTagEntries(tag)
.stream()
.map(UnificationEntry::value)
.collect(Collectors.toSet());
}
@Nullable
@Override
public TagKey<Item> getRelevantItemTag(ItemLike itemLike) {
if (!isRuntimeLoaded()) return null;
return getRuntimeOrThrow().getUnificationLookup().getRelevantItemTag(itemLike.asItem());
}
@Nullable
@Override
public Item getVariantItemTarget(ItemLike itemLike) {
if (!isRuntimeLoaded()) return null;
ResourceLocation id = BuiltInRegistries.ITEM.getKey(itemLike.asItem());
var replacement = getRuntimeOrThrow().getUnificationLookup().getVariantItemTarget(id);
return replacement == null ? null : replacement.value();
}
@Nullable
@Override
public Item getTagTargetItem(TagKey<Item> tag) {
if (!isRuntimeLoaded()) return null;
var replacement = getRuntimeOrThrow().getUnificationLookup().getTagTargetItem(tag);
return replacement == null ? null : replacement.value();
}
}

View file

@ -0,0 +1,306 @@
package com.almostreliable.unified.core;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.api.AlmostUnifiedRuntime;
import com.almostreliable.unified.api.unification.*;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry;
import com.almostreliable.unified.compat.PluginManager;
import com.almostreliable.unified.compat.viewer.ItemHider;
import com.almostreliable.unified.config.Config;
import com.almostreliable.unified.config.PlaceholderConfig;
import com.almostreliable.unified.config.TagConfig;
import com.almostreliable.unified.config.UnificationConfig;
import com.almostreliable.unified.unification.TagInheritance;
import com.almostreliable.unified.unification.TagSubstitutionsImpl;
import com.almostreliable.unified.unification.UnificationSettingsImpl;
import com.almostreliable.unified.unification.recipe.RecipeTransformer;
import com.almostreliable.unified.unification.recipe.RecipeUnifierRegistryImpl;
import com.almostreliable.unified.utils.DebugHandler;
import com.almostreliable.unified.utils.FileUtils;
import com.almostreliable.unified.utils.VanillaTagWrapper;
import com.google.gson.JsonElement;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.block.Block;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime {
private final Collection<? extends UnificationSettings> unificationSettings;
private final RecipeUnifierRegistry recipeUnifierRegistry;
private final TagSubstitutions tagSubstitutions;
private final Placeholders placeholders;
private final UnificationLookup compositeUnificationLookup;
private AlmostUnifiedRuntimeImpl(Collection<? extends UnificationSettings> unificationSettings, RecipeUnifierRegistry recipeUnifierRegistry, TagSubstitutions tagSubstitutions, Placeholders placeholders) {
this.unificationSettings = unificationSettings;
this.recipeUnifierRegistry = recipeUnifierRegistry;
this.tagSubstitutions = tagSubstitutions;
this.placeholders = placeholders;
this.compositeUnificationLookup = new CompositeUnificationLookup(unificationSettings);
}
public static AlmostUnifiedRuntimeImpl create(VanillaTagWrapper<Item> itemTags, VanillaTagWrapper<Block> blockTags) {
AlmostUnifiedCommon.LOGGER.warn("Reload detected. Reconstructing runtime.");
FileUtils.createGitIgnore();
var tagConfig = Config.load(TagConfig.NAME, TagConfig.SERIALIZER);
var placeholderConfig = Config.load(PlaceholderConfig.NAME, PlaceholderConfig.SERIALIZER);
var unificationConfigs = UnificationConfig.safeLoadConfigs();
var unificationTags = bakeAndValidateTags(unificationConfigs, itemTags, placeholderConfig);
RecipeUnifierRegistry recipeUnifierRegistry = new RecipeUnifierRegistryImpl();
PluginManager.instance().registerRecipeUnifiers(recipeUnifierRegistry);
// TODO: add plugin support for registering config defaults
TagReloadHandler.applyCustomTags(tagConfig.getCustomTags(), itemTags);
TagSubstitutionsImpl tagSubstitutions = TagSubstitutionsImpl.create(
itemTags::has,
unificationTags::contains,
tagConfig.getTagSubstitutions()
);
tagSubstitutions.apply(itemTags);
List<UnificationSettings> unificationSettings = createUnificationLookups(
itemTags,
blockTags,
unificationConfigs,
tagSubstitutions,
tagConfig.getTagInheritance()
);
ItemHider.applyHideTags(itemTags, unificationSettings, tagConfig.isEmiHidingStrict());
return new AlmostUnifiedRuntimeImpl(
unificationSettings,
recipeUnifierRegistry,
tagSubstitutions,
placeholderConfig
);
}
/**
* Bake all tags from unify configs and validate them.
* Validating contains:
* <ul>
* <li>Tag must exist in vanilla tags, which means that the tag is in used by either vanilla or any mods.</li>
* <li>Tag must not exist in another unify config. If found, the tag will be skipped.</li>
* </ul>
*
* @param unificationConfigs The unify configs
* @param itemTags The vanilla tags
* @param placeholders The replacements
* @return The baked tags combined from all unify configs
*/
private static Set<TagKey<Item>> bakeAndValidateTags(Collection<UnificationConfig> unificationConfigs, VanillaTagWrapper<Item> itemTags, Placeholders placeholders) {
Set<TagKey<Item>> result = new HashSet<>();
Map<TagKey<Item>, String> visitedTags = new HashMap<>();
Set<TagKey<Item>> wrongTags = new HashSet<>();
for (UnificationConfig config : unificationConfigs) {
Predicate<TagKey<Item>> validator = tag -> {
if (!itemTags.has(tag)) {
wrongTags.add(tag);
return false;
}
if (visitedTags.containsKey(tag)) {
AlmostUnifiedCommon.LOGGER.warn(
"Tag '{}' from unify config '{}' was already created in unify config '{}'",
config.getName(),
tag.location(),
visitedTags.get(tag));
return false;
}
visitedTags.put(tag, config.getName());
return true;
};
Set<TagKey<Item>> tags = config.bakeTags(validator, placeholders);
result.addAll(tags);
}
if (!wrongTags.isEmpty()) {
AlmostUnifiedCommon.LOGGER.warn("The following tags are invalid or not in use and will be ignored: {}",
wrongTags.stream().map(TagKey::location).collect(Collectors.toList()));
}
return result;
}
/**
* Creates all unify handlers for further unification.
* <p>
* This method also applies tag inheritance. If tag inheritance was applied, all handlers will be rebuilt due to tag inheritance modifications against vanilla tags.
*
* @param itemTags All existing item tags which are used ingame
* @param blockTags All existing block tags which are used ingame
* @param unificationConfigs All unify configs
* @param tagSubstitutions All tag substitutions
* @param tagInheritance Tag inheritance data
* @return All unify handlers
*/
private static List<UnificationSettings> createUnificationLookups(VanillaTagWrapper<Item> itemTags, VanillaTagWrapper<Block> blockTags, Collection<UnificationConfig> unificationConfigs, TagSubstitutionsImpl tagSubstitutions, TagInheritance tagInheritance) {
var unificationSettings = UnificationSettingsImpl.create(unificationConfigs,
itemTags,
blockTags,
tagSubstitutions);
var needsRebuild = tagInheritance.apply(itemTags, blockTags, unificationSettings);
if (needsRebuild) {
return UnificationSettingsImpl.create(unificationConfigs, itemTags, blockTags, tagSubstitutions);
}
return unificationSettings;
}
public void run(Map<ResourceLocation, JsonElement> recipes) {
DebugHandler debugHandler = DebugHandler.onRunStart(recipes, compositeUnificationLookup);
debugHandler.measure(() -> {
var transformer = new RecipeTransformer(recipeUnifierRegistry, unificationSettings);
return transformer.transformRecipes(recipes);
});
debugHandler.onRunEnd(recipes);
}
@Override
public UnificationLookup getUnificationLookup() {
return compositeUnificationLookup;
}
@Override
public Collection<? extends UnificationSettings> getUnificationSettings() {
return Collections.unmodifiableCollection(unificationSettings);
}
@Nullable
@Override
public UnificationSettings getUnificationSettings(String name) {
for (UnificationSettings settings : unificationSettings) {
if (settings.getName().equals(name)) {
return settings;
}
}
return null;
}
@Override
public TagSubstitutions getTagSubstitutions() {
return tagSubstitutions;
}
@Override
public Placeholders getPlaceholders() {
return placeholders;
}
private static final class CompositeUnificationLookup implements UnificationLookup {
private final Iterable<? extends UnificationLookup> unificationLookups;
@Nullable private Collection<TagKey<Item>> unificationTagsView;
private CompositeUnificationLookup(Iterable<? extends UnificationLookup> unificationLookups) {
this.unificationLookups = unificationLookups;
}
@Override
public Collection<TagKey<Item>> getTags() {
if (unificationTagsView == null) {
Set<TagKey<Item>> tagView = new HashSet<>();
for (var unificationLookup : unificationLookups) {
tagView.addAll(unificationLookup.getTags());
}
unificationTagsView = Collections.unmodifiableCollection(tagView);
}
return unificationTagsView;
}
@Override
public Collection<UnificationEntry<Item>> getTagEntries(TagKey<Item> tag) {
for (var unificationLookup : unificationLookups) {
var resultItems = unificationLookup.getTagEntries(tag);
if (!resultItems.isEmpty()) {
return resultItems;
}
}
return Collections.emptyList();
}
@Nullable
@Override
public UnificationEntry<Item> getItemEntry(ResourceLocation item) {
for (var unificationLookup : unificationLookups) {
var resultItem = unificationLookup.getItemEntry(item);
if (resultItem != null) {
return resultItem;
}
}
return null;
}
@Nullable
@Override
public TagKey<Item> getRelevantItemTag(ResourceLocation item) {
for (var unificationLookup : unificationLookups) {
TagKey<Item> tag = unificationLookup.getRelevantItemTag(item);
if (tag != null) {
return tag;
}
}
return null;
}
@Override
public UnificationEntry<Item> getVariantItemTarget(ResourceLocation item) {
for (var unificationLookup : unificationLookups) {
var resultItem = unificationLookup.getVariantItemTarget(item);
if (resultItem != null) {
return resultItem;
}
}
return null;
}
@Override
public UnificationEntry<Item> getTagTargetItem(TagKey<Item> tag, Predicate<ResourceLocation> itemFilter) {
for (var unificationLookup : unificationLookups) {
var result = unificationLookup.getTagTargetItem(tag, itemFilter);
if (result != null) {
return result;
}
}
return null;
}
@Override
public boolean isUnifiedIngredientItem(Ingredient ingredient, ItemStack item) {
for (var unificationLookup : unificationLookups) {
if (unificationLookup.isUnifiedIngredientItem(ingredient, item)) {
return true;
}
}
return false;
}
}
}

View file

@ -0,0 +1,23 @@
package com.almostreliable.unified.core;
import com.almostreliable.unified.api.constant.ModConstants;
import com.almostreliable.unified.api.plugin.AlmostUnifiedNeoPlugin;
import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin;
import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry;
import com.almostreliable.unified.compat.unification.GregTechModernRecipeUnifier;
import com.almostreliable.unified.utils.Utils;
import net.minecraft.resources.ResourceLocation;
@AlmostUnifiedNeoPlugin
public class CommonPlugin implements AlmostUnifiedPlugin {
@Override
public ResourceLocation getPluginId() {
return Utils.getRL("common");
}
@Override
public void registerRecipeUnifiers(RecipeUnifierRegistry registry) {
registry.registerForModId(ModConstants.GREGTECH_MODERN, new GregTechModernRecipeUnifier());
}
}

View file

@ -0,0 +1,99 @@
package com.almostreliable.unified.core;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.utils.VanillaTagWrapper;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
@SuppressWarnings({ "StaticVariableMayNotBeInitialized", "StaticVariableUsedBeforeInitialization" })
public final class TagReloadHandler {
private static final Object LOCK = new Object();
@Nullable private static VanillaTagWrapper<Item> VANILLA_ITEM_TAGS;
@Nullable private static VanillaTagWrapper<Block> VANILLA_BLOCK_TAGS;
private TagReloadHandler() {}
public static void initItemTags(Map<ResourceLocation, Collection<Holder<Item>>> rawItemTags) {
synchronized (LOCK) {
VANILLA_ITEM_TAGS = VanillaTagWrapper.of(BuiltInRegistries.ITEM, rawItemTags);
}
}
public static void initBlockTags(Map<ResourceLocation, Collection<Holder<Block>>> rawBlockTags) {
synchronized (LOCK) {
VANILLA_BLOCK_TAGS = VanillaTagWrapper.of(BuiltInRegistries.BLOCK, rawBlockTags);
}
}
public static void run() {
if (VANILLA_ITEM_TAGS == null || VANILLA_BLOCK_TAGS == null) {
return;
}
AlmostUnifiedCommon.onTagLoaderReload(VANILLA_ITEM_TAGS, VANILLA_BLOCK_TAGS);
VANILLA_ITEM_TAGS.seal();
VANILLA_BLOCK_TAGS.seal();
VANILLA_ITEM_TAGS = null;
VANILLA_BLOCK_TAGS = null;
}
public static void applyCustomTags(Map<ResourceLocation, Set<ResourceLocation>> customTags, VanillaTagWrapper<Item> itemTags) {
Multimap<ResourceLocation, ResourceLocation> changedItemTags = HashMultimap.create();
for (var entry : customTags.entrySet()) {
ResourceLocation tag = entry.getKey();
Set<ResourceLocation> itemIds = entry.getValue();
for (ResourceLocation itemId : itemIds) {
if (!BuiltInRegistries.ITEM.containsKey(itemId)) {
AlmostUnifiedCommon.LOGGER.warn("[CustomTags] Custom tag '{}' contains invalid item '{}'",
tag,
itemId);
continue;
}
ResourceKey<Item> itemKey = ResourceKey.create(Registries.ITEM, itemId);
Holder<Item> itemHolder = BuiltInRegistries.ITEM.getHolder(itemKey).orElse(null);
if (itemHolder == null) continue;
var currentHolders = itemTags.get(tag);
if (!currentHolders.isEmpty()) {
if (currentHolders.contains(itemHolder)) {
AlmostUnifiedCommon.LOGGER.warn("[CustomTags] Custom tag '{}' already contains item '{}'",
tag,
itemId);
continue;
}
}
itemTags.add(tag, itemHolder);
changedItemTags.put(tag, itemId);
}
}
if (!changedItemTags.isEmpty()) {
changedItemTags.asMap().forEach(
(tag, items) -> AlmostUnifiedCommon.LOGGER.info(
"[CustomTags] Modified tag '#{}', added {}",
tag,
items
)
);
}
}
}

View file

@ -1,5 +1,5 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.mixin;
package com.almostreliable.unified.core;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

@ -0,0 +1,29 @@
package com.almostreliable.unified.mixin.loot;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.unification.loot.LootUnificationHandler;
import net.minecraft.world.level.storage.loot.entries.CompositeEntryBase;
import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
@Mixin(CompositeEntryBase.class)
public class CompositeEntryBaseMixin implements LootUnificationHandler {
@Shadow @Final protected List<LootPoolEntryContainer> children;
@Override
public boolean almostunified$unify(UnificationLookup lookup) {
boolean unified = false;
for (LootPoolEntryContainer child : children) {
if (child instanceof LootUnificationHandler handler) {
unified |= handler.almostunified$unify(lookup);
}
}
return unified;
}
}

View file

@ -0,0 +1,27 @@
package com.almostreliable.unified.mixin.loot;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.unification.loot.LootUnificationHandler;
import net.minecraft.core.Holder;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.storage.loot.entries.LootItem;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(LootItem.class)
public class LootItemMixin implements LootUnificationHandler {
@Shadow @Final @Mutable private Holder<Item> item;
@Override
public boolean almostunified$unify(UnificationLookup lookup) {
var replacement = lookup.getVariantItemTarget(item);
if (replacement == null || item.value().equals(replacement.value())) {
return false;
}
this.item = replacement.asHolderOrThrow();
return true;
}
}

View file

@ -0,0 +1,30 @@
package com.almostreliable.unified.mixin.loot;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.unification.loot.LootUnificationHandler;
import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
@Mixin(LootPool.class)
public class LootPoolMixin implements LootUnificationHandler {
@Shadow @Final private List<LootPoolEntryContainer> entries;
@Override
public boolean almostunified$unify(UnificationLookup lookup) {
boolean unified = false;
for (LootPoolEntryContainer entry : this.entries) {
if (entry instanceof LootUnificationHandler handler) {
unified |= handler.almostunified$unify(lookup);
}
}
return unified;
}
}

View file

@ -0,0 +1,26 @@
package com.almostreliable.unified.mixin.loot;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.unification.loot.LootUnificationHandler;
import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.LootTable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
@Mixin(LootTable.class)
public class LootTableMixin implements LootUnificationHandler {
@Shadow @Final private List<LootPool> pools;
@Override
public boolean almostunified$unify(UnificationLookup lookup) {
boolean unified = false;
for (LootPool pool : pools) {
unified |= LootUnificationHandler.cast(pool).almostunified$unify(lookup);
}
return unified;
}
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.mixin.loot;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -1,8 +1,8 @@
package com.almostreliable.unified.mixin.runtime;
import com.almostreliable.unified.ClientTagUpdateEvent;
import com.almostreliable.unified.utils.ClientTagUpdateEvent;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.protocol.game.ClientboundUpdateTagsPacket;
import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;

View file

@ -1,12 +1,15 @@
package com.almostreliable.unified.mixin.runtime;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.google.gson.JsonElement;
import net.minecraft.core.HolderLookup;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.item.crafting.RecipeManager;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@ -17,12 +20,14 @@ import java.util.Map;
@Mixin(value = RecipeManager.class, priority = 1_099)
public class RecipeManagerMixin {
@Shadow @Final private HolderLookup.Provider registries;
@Inject(method = "apply(Ljava/util/Map;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V", at = @At("HEAD"))
private void runTransformation(Map<ResourceLocation, JsonElement> recipes, ResourceManager resourceManager, ProfilerFiller profiler, CallbackInfo ci) {
try {
AlmostUnified.onRecipeManagerReload(recipes);
AlmostUnifiedCommon.onRecipeManagerReload(recipes, registries);
} catch (Exception e) {
AlmostUnified.LOG.error(e.getMessage(), e);
AlmostUnifiedCommon.LOGGER.error(e.getMessage(), e);
}
}
}

View file

@ -1,7 +1,7 @@
package com.almostreliable.unified.mixin.runtime;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.utils.TagReloadHandler;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.core.TagReloadHandler;
import com.almostreliable.unified.utils.Utils;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
@ -26,22 +26,22 @@ public class TagLoaderMixin {
@Inject(method = "build(Ljava/util/Map;)Ljava/util/Map;", at = @At("RETURN"))
private <T> void onCreateLoadResult(Map<ResourceLocation, List<TagLoader.EntryWithSource>> map, CallbackInfoReturnable<Map<ResourceLocation, Collection<T>>> cir) {
if (directory.equals("tags/items")) {
if (directory.equals("tags/item")) {
try {
Map<ResourceLocation, Collection<Holder<Item>>> tags = Utils.cast(cir.getReturnValue());
TagReloadHandler.initItemTags(tags);
TagReloadHandler.run();
} catch (Exception e) {
AlmostUnified.LOG.error(e.getMessage(), e);
AlmostUnifiedCommon.LOGGER.error(e.getMessage(), e);
}
}
if (directory.equals("tags/blocks")) {
if (directory.equals("tags/block")) {
try {
Map<ResourceLocation, Collection<Holder<Block>>> tags = Utils.cast(cir.getReturnValue());
TagReloadHandler.initBlockTags(tags);
TagReloadHandler.run();
} catch (Exception e) {
AlmostUnified.LOG.error(e.getMessage(), e);
AlmostUnifiedCommon.LOGGER.error(e.getMessage(), e);
}
}
}

View file

@ -1,9 +1,12 @@
package com.almostreliable.unified.mixin.unifier;
package com.almostreliable.unified.mixin.unification;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.api.AlmostUnified;
import com.almostreliable.unified.api.AlmostUnifiedRuntime;
import net.minecraft.core.Holder;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ArmorMaterial;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@ -14,14 +17,16 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ArmorItem.class)
public class ArmorItemMixin {
@Shadow @Final protected ArmorMaterial material;
@Shadow @Final protected Holder<ArmorMaterial> material;
@Inject(method = "isValidRepairItem", at = @At("HEAD"), cancellable = true)
private void unified$repairUnification(ItemStack stack, ItemStack repairCandidate, CallbackInfoReturnable<Boolean> cir) {
AlmostUnified.getRuntime().getReplacementMap().ifPresent(replacementMap -> {
if (replacementMap.isItemInUnifiedIngredient(material.getRepairIngredient(), repairCandidate)) {
AlmostUnifiedRuntime runtime = AlmostUnified.INSTANCE.getRuntime();
if (runtime == null) return;
Ingredient repairIngredient = material.value().repairIngredient().get();
if (runtime.getUnificationLookup().isUnifiedIngredientItem(repairIngredient, repairCandidate)) {
cir.setReturnValue(true);
}
});
}
}

View file

@ -1,9 +1,11 @@
package com.almostreliable.unified.mixin.unifier;
package com.almostreliable.unified.mixin.unification;
import com.almostreliable.unified.AlmostUnified;
import com.almostreliable.unified.api.AlmostUnified;
import com.almostreliable.unified.api.AlmostUnifiedRuntime;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Tier;
import net.minecraft.world.item.TieredItem;
import net.minecraft.world.item.crafting.Ingredient;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@ -18,10 +20,12 @@ public class TieredItemMixin {
@Inject(method = "isValidRepairItem", at = @At("HEAD"), cancellable = true)
private void unified$repairUnification(ItemStack stack, ItemStack repairCandidate, CallbackInfoReturnable<Boolean> cir) {
AlmostUnified.getRuntime().getReplacementMap().ifPresent(replacementMap -> {
if (replacementMap.isItemInUnifiedIngredient(tier.getRepairIngredient(), repairCandidate)) {
AlmostUnifiedRuntime runtime = AlmostUnified.INSTANCE.getRuntime();
if (runtime == null) return;
Ingredient repairIngredient = tier.getRepairIngredient();
if (runtime.getUnificationLookup().isUnifiedIngredientItem(repairIngredient, repairCandidate)) {
cir.setReturnValue(true);
}
});
}
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.mixin.unification;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -1,168 +0,0 @@
package com.almostreliable.unified.recipe;
import com.almostreliable.unified.api.recipe.RecipeConstants;
import com.almostreliable.unified.api.recipe.RecipeContext;
import com.almostreliable.unified.utils.JsonUtils;
import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.UnifyTag;
import com.almostreliable.unified.utils.Utils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import javax.annotation.Nullable;
import java.util.function.Predicate;
@SuppressWarnings("SameParameterValue")
public class RecipeContextImpl implements RecipeContext {
private final ReplacementMap replacementMap;
private final JsonObject originalRecipe;
public RecipeContextImpl(JsonObject json, ReplacementMap replacementMap) {
this.originalRecipe = json;
this.replacementMap = replacementMap;
}
@Nullable
@Override
public ResourceLocation getReplacementForItem(@Nullable ResourceLocation item) {
if (item == null) {
return null;
}
return replacementMap.getReplacementForItem(item);
}
@Nullable
@Override
public ResourceLocation getPreferredItemForTag(@Nullable UnifyTag<Item> tag, Predicate<ResourceLocation> filter) {
if (tag == null) {
return null;
}
return replacementMap.getPreferredItemForTag(tag, filter);
}
@Nullable
@Override
public UnifyTag<Item> getPreferredTagForItem(@Nullable ResourceLocation item) {
if (item == null) {
return null;
}
return replacementMap.getPreferredTagForItem(item);
}
@Nullable
@Override
public JsonElement createIngredientReplacement(@Nullable JsonElement element) {
if (element == null) {
return null;
}
JsonElement copy = element.deepCopy();
tryCreateIngredientReplacement(copy);
return element.equals(copy) ? null : copy;
}
private void tryCreateIngredientReplacement(@Nullable JsonElement element) {
if (element instanceof JsonArray array) {
for (JsonElement e : array) {
tryCreateIngredientReplacement(e);
}
}
if (element instanceof JsonObject object) {
tryCreateIngredientReplacement(object.get(RecipeConstants.VALUE));
tryCreateIngredientReplacement(object.get(RecipeConstants.BASE));
tryCreateIngredientReplacement(object.get(RecipeConstants.INGREDIENT));
if (object.get(RecipeConstants.TAG) instanceof JsonPrimitive primitive) {
UnifyTag<Item> tag = Utils.toItemTag(primitive.getAsString());
var ownerTag = replacementMap.getTagOwnerships().getOwnerByTag(tag);
if (ownerTag != null) {
object.addProperty(RecipeConstants.TAG, ownerTag.location().toString());
}
}
if (object.get(RecipeConstants.ITEM) instanceof JsonPrimitive primitive) {
ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString());
UnifyTag<Item> tag = getPreferredTagForItem(item);
if (tag != null) {
object.remove(RecipeConstants.ITEM);
object.addProperty(RecipeConstants.TAG, tag.location().toString());
}
}
}
}
@Override
@Nullable
public JsonElement createResultReplacement(@Nullable JsonElement element) {
return createResultReplacement(element, true, RecipeConstants.ITEM);
}
@Override
@Nullable
public JsonElement createResultReplacement(@Nullable JsonElement element, boolean tagLookup, String... lookupKeys) {
if (element == null) {
return null;
}
JsonElement copy = element.deepCopy();
JsonElement result = tryCreateResultReplacement(copy, tagLookup, lookupKeys);
return element.equals(result) ? null : result;
}
@Nullable
private JsonElement tryCreateResultReplacement(JsonElement element, boolean tagLookup, String... lookupKeys) {
if (element instanceof JsonPrimitive primitive) {
ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString());
ResourceLocation replacement = getReplacementForItem(item);
if (replacement != null) {
return new JsonPrimitive(replacement.toString());
}
return null;
}
if (element instanceof JsonArray array &&
JsonUtils.replaceOn(array, j -> tryCreateResultReplacement(j, tagLookup, lookupKeys))) {
return element;
}
if (element instanceof JsonObject object) {
for (String key : lookupKeys) {
if (JsonUtils.replaceOn(object, key, j -> tryCreateResultReplacement(j, tagLookup, lookupKeys))) {
return element;
}
}
// when tags are used as outputs, replace them with the preferred item
if (tagLookup && object.get(RecipeConstants.TAG) instanceof JsonPrimitive primitive) {
ResourceLocation item = getPreferredItemForTag(Utils.toItemTag(primitive.getAsString()), $ -> true);
if (item != null) {
object.remove(RecipeConstants.TAG);
object.addProperty(RecipeConstants.ITEM, item.toString());
}
return element;
}
}
return null;
}
@Override
public ResourceLocation getType() {
String type = originalRecipe.get("type").getAsString();
return new ResourceLocation(type);
}
@Override
public boolean hasProperty(String property) {
return originalRecipe.has(property);
}
}

View file

@ -1,174 +0,0 @@
package com.almostreliable.unified.recipe;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.utils.FileUtils;
import net.minecraft.resources.ResourceLocation;
import org.apache.commons.lang3.StringUtils;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class RecipeDumper {
private final RecipeTransformer.Result result;
private final long startTime;
private final long endTime;
private final DateFormat format = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
public RecipeDumper(RecipeTransformer.Result result, long startTime, long endTime) {
this.result = result;
this.startTime = startTime;
this.endTime = endTime;
}
public void dump(boolean dumpOverview, boolean dumpUnify, boolean dumpDuplicate) {
String last = "# Last execution: " + format.format(new Date(startTime));
if (dumpOverview) {
FileUtils.write(AlmostUnifiedPlatform.INSTANCE.getLogPath(), "overview_dump.txt", sb -> {
sb.append(last).append("\n");
dumpOverview(sb);
});
}
if (dumpUnify) {
FileUtils.write(AlmostUnifiedPlatform.INSTANCE.getLogPath(), "unify_dump.txt", sb -> {
sb.append(last).append("\n");
dumpUnifyRecipes(sb);
});
}
if (dumpDuplicate) {
FileUtils.write(AlmostUnifiedPlatform.INSTANCE.getLogPath(), "duplicates_dump.txt", sb -> {
sb.append(last).append("\n");
dumpDuplicates(sb);
});
}
}
private void dumpDuplicates(StringBuilder stringBuilder) {
getSortedUnifiedRecipeTypes().forEach(type -> {
Collection<RecipeLink.DuplicateLink> duplicates = result
.getDuplicates(type)
.stream()
.sorted(Comparator.comparing(l -> l.getMaster().getId().toString()))
.toList();
if (duplicates.isEmpty()) return;
stringBuilder.append(duplicates
.stream()
.map(this::createDuplicatesDump)
.collect(Collectors.joining("", type + " {\n", "}\n\n")));
});
}
private String createDuplicatesDump(RecipeLink.DuplicateLink link) {
return link
.getRecipes()
.stream()
.sorted(Comparator.comparing(r -> r.getId().toString()))
.map(r -> "\t\t- " + r.getId() + "\n")
.collect(Collectors.joining("", String.format("\t%s\n", link.getMaster().getId().toString()), "\n"));
}
private void dumpOverview(StringBuilder stringBuilder) {
stringBuilder
.append("# Overview:\n")
.append("\t- Unified: ")
.append(result.getUnifiedRecipeCount())
.append("\n")
.append("\t- Duplicates: ")
.append(result.getDuplicatesCount())
.append(" (Individual: ")
.append(result.getDuplicateRecipesCount())
.append(")\n")
.append("\t- Total Recipes: ")
.append(result.getRecipeCount())
.append("\n")
.append("\t- Elapsed Time: ")
.append(getTotalTime())
.append("ms")
.append("\n\n")
.append("# Summary: \n");
stringBuilder
.append(rf("Recipe type", 45))
.append(" | ")
.append(lf("Unifies", 10))
.append(" | ")
.append(lf("Duplicates", 10))
.append(" | ")
.append(lf("All", 5))
.append("\n")
.append(StringUtils.repeat("-", 45 + 10 + 10 + 5 + 9))
.append("\n");
getSortedUnifiedRecipeTypes().forEach(type -> {
int unifiedSize = result.getUnifiedRecipes(type).size();
int allSize = result.getRecipes(type).size();
int duplicatesSize = result.getDuplicates(type).size();
int individualDuplicatesSize = result
.getDuplicates(type)
.stream()
.mapToInt(l -> l.getRecipes().size())
.sum();
String dStr = String.format("%s (%s)", lf(duplicatesSize, 3), lf(individualDuplicatesSize, 3));
stringBuilder
.append(rf(type, 45))
.append(" | ")
.append(lf(unifiedSize, 10))
.append(" | ")
.append(lf(duplicatesSize == 0 ? " " : dStr, 10))
.append(" | ")
.append(lf(allSize, 5))
.append("\n");
});
}
private void dumpUnifyRecipes(StringBuilder stringBuilder) {
getSortedUnifiedRecipeTypes().forEach(type -> {
stringBuilder.append(type.toString()).append(" {\n");
getSortedUnifiedRecipes(type).forEach(recipe -> {
stringBuilder
.append("\t- ")
.append(recipe.getId())
.append("\n")
.append("\t\t Original: ")
.append(recipe.getOriginal().toString())
.append("\n")
.append("\t\t Transformed: ")
.append(recipe.getUnified() == null ? "NOT UNIFIED" : recipe.getUnified().toString())
.append("\n\n");
});
stringBuilder.append("}\n\n");
});
}
private String rf(Object v, int size) {
return StringUtils.rightPad(v.toString(), size);
}
private String lf(Object v, int size) {
return StringUtils.leftPad(v.toString(), size);
}
private Stream<ResourceLocation> getSortedUnifiedRecipeTypes() {
return result.getUnifiedRecipeTypes().stream().sorted(Comparator.comparing(ResourceLocation::toString));
}
private Stream<RecipeLink> getSortedUnifiedRecipes(ResourceLocation type) {
return result.getUnifiedRecipes(type).stream().sorted(Comparator.comparing(r -> r.getId().toString()));
}
private long getTotalTime() {
return endTime - startTime;
}
}

View file

@ -1,94 +0,0 @@
package com.almostreliable.unified.recipe;
import com.almostreliable.unified.api.recipe.RecipeContext;
import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
public class RecipeUnifierBuilderImpl implements RecipeUnifierBuilder {
private final Map<String, Entry<?>> consumers = new HashMap<>();
@Override
public void forEachObject(String property, BiFunction<JsonObject, RecipeContext, JsonObject> consumer) {
BiFunction<JsonArray, RecipeContext, JsonArray> arrayConsumer = (array, ctx) -> {
for (int i = 0; i < array.size(); i++) {
JsonElement element = array.get(i);
if (element instanceof JsonObject obj) {
JsonObject result = consumer.apply(obj, ctx);
if (result != null) {
array.set(i, result);
}
}
}
return array;
};
put(property, JsonArray.class, arrayConsumer);
}
@Override
public void put(String property, BiFunction<JsonElement, RecipeContext, JsonElement> consumer) {
consumers.put(property, new Entry<>(JsonElement.class, consumer));
}
@Override
public <T extends JsonElement> void put(String property, Class<T> type, BiFunction<T, RecipeContext, T> consumer) {
consumers.put(property, new Entry<>(type, consumer));
}
@Nullable
public JsonObject unify(JsonObject json, RecipeContext context) {
JsonObject changedValues = new JsonObject();
for (var e : json.entrySet()) {
Entry<?> consumer = consumers.get(e.getKey());
if (consumer != null) {
JsonElement currentElement = e.getValue();
JsonElement transformedElement = consumer.apply(currentElement.deepCopy(), context);
if (transformedElement != null && !transformedElement.equals(currentElement)) {
changedValues.add(e.getKey(), transformedElement);
}
}
}
if (changedValues.size() == 0) {
return null;
}
// helps to preserve the order of the elements
JsonObject result = new JsonObject();
for (var entry : json.entrySet()) {
JsonElement changedValue = changedValues.get(entry.getKey());
if (changedValue != null) {
result.add(entry.getKey(), changedValue);
} else {
result.add(entry.getKey(), entry.getValue());
}
}
return result;
}
public Collection<String> getKeys() {
return consumers.keySet();
}
private record Entry<T extends JsonElement>(Class<T> expectedType,
BiFunction<T, RecipeContext, T> func) {
@Nullable
T apply(JsonElement json, RecipeContext context) {
if (expectedType.isInstance(json)) {
return func.apply(expectedType.cast(json), context);
}
return null;
}
}
}

View file

@ -1,36 +0,0 @@
package com.almostreliable.unified.recipe.unifier;
import com.almostreliable.unified.api.recipe.RecipeConstants;
import com.almostreliable.unified.api.recipe.RecipeUnifier;
import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder;
import java.util.Set;
public class GenericRecipeUnifier implements RecipeUnifier {
public static final GenericRecipeUnifier INSTANCE = new GenericRecipeUnifier();
private static final Set<String> INPUT_KEYS = Set.of(
RecipeConstants.INPUT,
RecipeConstants.INPUTS,
RecipeConstants.INGREDIENT,
RecipeConstants.INGREDIENTS,
RecipeConstants.INPUT_ITEMS
);
private static final Set<String> OUTPUT_KEYS = Set.of(
RecipeConstants.OUTPUT,
RecipeConstants.OUTPUTS,
RecipeConstants.RESULT,
RecipeConstants.RESULTS,
RecipeConstants.OUTPUT_ITEMS
);
@Override
public void collectUnifier(RecipeUnifierBuilder builder) {
for (String inputKey : INPUT_KEYS) {
builder.put(inputKey, (json, ctx) -> ctx.createIngredientReplacement(json));
}
for (String outputKey : OUTPUT_KEYS) {
builder.put(outputKey, (json, ctx) -> ctx.createResultReplacement(json));
}
}
}

View file

@ -1,52 +0,0 @@
package com.almostreliable.unified.recipe.unifier;
import com.almostreliable.unified.api.recipe.RecipeConstants;
import com.almostreliable.unified.api.recipe.RecipeContext;
import com.almostreliable.unified.api.recipe.RecipeUnifier;
import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder;
import net.minecraft.resources.ResourceLocation;
import java.util.HashMap;
import java.util.Map;
public class RecipeHandlerFactory {
private static final ResourceLocation SMITHING_TYPE = new ResourceLocation("minecraft:smithing");
private final Map<ResourceLocation, RecipeUnifier> transformersByType = new HashMap<>();
private final Map<String, RecipeUnifier> transformersByModId = new HashMap<>();
public void fillUnifier(RecipeUnifierBuilder builder, RecipeContext context) {
GenericRecipeUnifier.INSTANCE.collectUnifier(builder);
if (context.hasProperty(ShapedRecipeKeyUnifier.PATTERN_PROPERTY) &&
context.hasProperty(ShapedRecipeKeyUnifier.KEY_PROPERTY)) {
ShapedRecipeKeyUnifier.INSTANCE.collectUnifier(builder);
}
if (context.hasProperty(SmithingRecipeUnifier.ADDITION_PROPERTY) &&
context.hasProperty(SmithingRecipeUnifier.BASE_PROPERTY) &&
context.hasProperty(RecipeConstants.RESULT)) {
SmithingRecipeUnifier.INSTANCE.collectUnifier(builder);
}
ResourceLocation type = context.getType();
RecipeUnifier byMod = transformersByModId.get(type.getNamespace());
if (byMod != null) {
byMod.collectUnifier(builder);
}
RecipeUnifier byType = transformersByType.get(type);
if (byType != null) {
byType.collectUnifier(builder);
}
}
public void registerForType(ResourceLocation type, RecipeUnifier transformer) {
transformersByType.put(type, transformer);
}
public void registerForMod(String mod, RecipeUnifier transformer) {
transformersByModId.put(mod, transformer);
}
}

View file

@ -1,25 +0,0 @@
package com.almostreliable.unified.recipe.unifier;
import com.almostreliable.unified.api.recipe.RecipeUnifier;
import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
public class ShapedRecipeKeyUnifier implements RecipeUnifier {
public static final RecipeUnifier INSTANCE = new ShapedRecipeKeyUnifier();
public static final String PATTERN_PROPERTY = "pattern";
public static final String KEY_PROPERTY = "key";
@Override
public void collectUnifier(RecipeUnifierBuilder builder) {
builder.put(KEY_PROPERTY, JsonObject.class, (json, context) -> {
for (var entry : json.entrySet()) {
JsonElement result = context.createIngredientReplacement(entry.getValue());
if (result != null) {
entry.setValue(result);
}
}
return json;
});
}
}

View file

@ -1,20 +0,0 @@
package com.almostreliable.unified.recipe.unifier;
import com.almostreliable.unified.api.recipe.RecipeConstants;
import com.almostreliable.unified.api.recipe.RecipeUnifier;
import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder;
public class SmithingRecipeUnifier implements RecipeUnifier {
public static final RecipeUnifier INSTANCE = new SmithingRecipeUnifier();
public static final String ADDITION_PROPERTY = "addition";
public static final String BASE_PROPERTY = "base";
@Override
public void collectUnifier(RecipeUnifierBuilder builder) {
builder.put(ADDITION_PROPERTY, (json, ctx) -> ctx.createIngredientReplacement(json));
builder.put(BASE_PROPERTY, (json, ctx) -> ctx.createIngredientReplacement(json));
builder.put(RecipeConstants.RESULT, (json, ctx) -> ctx.createResultReplacement(json));
}
}

View file

@ -1,6 +0,0 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.almostreliable.unified.recipe.unifier;
import net.minecraft.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,78 @@
package com.almostreliable.unified.unification;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.api.unification.ModPriorities;
import com.almostreliable.unified.api.unification.UnificationEntry;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import javax.annotation.Nullable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public final class ModPrioritiesImpl implements ModPriorities {
private final List<String> modPriorities;
private final Map<TagKey<Item>, String> priorityOverrides;
public ModPrioritiesImpl(List<String> modPriorities, Map<TagKey<Item>, String> priorityOverrides) {
this.modPriorities = modPriorities;
this.priorityOverrides = priorityOverrides;
}
@Nullable
@Override
public String getPriorityOverride(TagKey<Item> tag) {
return priorityOverrides.get(tag);
}
@Nullable
@Override
public UnificationEntry<Item> findPriorityOverrideItem(TagKey<Item> tag, List<UnificationEntry<Item>> items) {
String priorityOverride = getPriorityOverride(tag);
if (priorityOverride == null) return null;
var entry = findItemByNamespace(items, priorityOverride);
if (entry != null) return entry;
AlmostUnifiedCommon.LOGGER.warn(
"Priority override mod '{}' for tag '{}' does not contain a valid item. Falling back to default priority.",
priorityOverride,
tag.location()
);
return null;
}
@Nullable
@Override
public UnificationEntry<Item> findTargetItem(TagKey<Item> tag, List<UnificationEntry<Item>> items) {
var overrideEntry = findPriorityOverrideItem(tag, items);
if (overrideEntry != null) {
return overrideEntry;
}
for (String modPriority : modPriorities) {
var entry = findItemByNamespace(items, modPriority);
if (entry != null) return entry;
}
return null;
}
@Override
public Iterator<String> iterator() {
return modPriorities.iterator();
}
@Nullable
private static UnificationEntry<Item> findItemByNamespace(List<UnificationEntry<Item>> items, String namespace) {
for (var item : items) {
if (item.id().getNamespace().equals(namespace)) {
return item;
}
}
return null;
}
}

View file

@ -0,0 +1,148 @@
package com.almostreliable.unified.unification;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.api.unification.StoneVariants;
import com.almostreliable.unified.utils.VanillaTagWrapper;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import java.util.*;
import java.util.regex.Pattern;
public final class StoneVariantsImpl implements StoneVariants {
private static final Pattern ORE_TAG_PATTERN = Pattern.compile("(c:ores/.+|(minecraft|c):.+_ores)");
private final Map<TagKey<Item>, Boolean> isOreTagCache = new HashMap<>();
private final List<String> stoneVariants;
private final Map<ResourceLocation, String> itemToStoneVariant;
private StoneVariantsImpl(Collection<String> stoneVariants, Map<ResourceLocation, String> itemToStoneVariant) {
this.stoneVariants = sortStoneVariants(stoneVariants);
this.itemToStoneVariant = itemToStoneVariant;
}
public static StoneVariants create(Collection<String> stoneVariants, VanillaTagWrapper<Item> itemTags, VanillaTagWrapper<Block> blockTags) {
Set<TagKey<Item>> stoneVariantItemTags = new HashSet<>();
Set<TagKey<Block>> stoneVariantBlockTags = new HashSet<>();
for (String stoneVariant : stoneVariants) {
ResourceLocation id = ResourceLocation.fromNamespaceAndPath("c", "ores_in_ground/" + stoneVariant);
stoneVariantItemTags.add(TagKey.create(Registries.ITEM, id));
stoneVariantBlockTags.add(TagKey.create(Registries.BLOCK, id));
}
var itemToStoneVariantTag = mapEntriesToStoneVariantTags(stoneVariantItemTags, itemTags);
var blockToStoneVariantTag = mapEntriesToStoneVariantTags(stoneVariantBlockTags, blockTags);
Map<ResourceLocation, String> itemToStoneVariant = new HashMap<>();
itemToStoneVariantTag.forEach((item, tag) -> {
String itemStoneVariant = getVariantFromStoneVariantTag(tag);
var blockTagFromItem = blockToStoneVariantTag.get(item);
if (blockTagFromItem != null) {
String blockStoneVariant = getVariantFromStoneVariantTag(blockTagFromItem);
if (blockStoneVariant.length() > itemStoneVariant.length()) {
itemToStoneVariant.put(item, blockStoneVariant);
return;
}
}
itemToStoneVariant.put(item, itemStoneVariant);
});
return new StoneVariantsImpl(stoneVariants, itemToStoneVariant);
}
/**
* Maps all entries of a stone variant tag to its respective stone variant tag.
*
* @param stoneVariantTags the stone variant tags
* @param tags the vanilla tag wrapper to get the tag entries from
* @param <T> the tag type
* @return the entry to stone variant tag mapping
*/
private static <T> Map<ResourceLocation, TagKey<T>> mapEntriesToStoneVariantTags(Set<TagKey<T>> stoneVariantTags, VanillaTagWrapper<T> tags) {
Map<ResourceLocation, TagKey<T>> idToStoneVariantTag = new HashMap<>();
for (var stoneVariantTag : stoneVariantTags) {
for (var holder : tags.get(stoneVariantTag)) {
ResourceLocation id = holder
.unwrapKey()
.orElseThrow(() -> new IllegalStateException("Tag is not bound for holder " + holder))
.location();
var oldTag = idToStoneVariantTag.put(id, stoneVariantTag);
if (oldTag != null) {
AlmostUnifiedCommon.LOGGER.error(
"{} is bound to multiple stone variant tags: {} and {}",
id,
oldTag,
stoneVariantTag
);
}
}
}
return idToStoneVariantTag;
}
/**
* Returns the variant of a stone variant tag.
* <p>
* Example: {@code c:ores_in_ground/deepslate} -> {@code deepslate}
*
* @param tag the stone variant tag
* @return the stone variant
*/
private static String getVariantFromStoneVariantTag(TagKey<?> tag) {
String tagString = tag.location().toString();
int i = tagString.lastIndexOf('/');
String stoneVariant = tagString.substring(i + 1);
stoneVariant = stoneVariant.equals("stone") ? "" : stoneVariant;
return stoneVariant;
}
@Override
public String getStoneVariant(ResourceLocation item) {
return itemToStoneVariant.computeIfAbsent(item, this::computeStoneVariant);
}
@Override
public boolean isOreTag(TagKey<Item> tag) {
return isOreTagCache.computeIfAbsent(tag, t -> ORE_TAG_PATTERN.matcher(t.location().toString()).matches());
}
/**
* Returns a list of all stone variants sorted from longest to shortest.
* <p>
* This is required to ensure that the longest variant is returned first and no sub-matches happen.<br>
* Example: "nether" and "blue_nether" would both match "nether" if the list is not sorted.
*
* @param stoneVariants the stone variants to sort
* @return the sorted stone variants
*/
private static List<String> sortStoneVariants(Collection<String> stoneVariants) {
return stoneVariants.stream().sorted(Comparator.comparingInt(String::length).reversed()).toList();
}
/**
* Implementation logic for caching in {@link #getStoneVariant(ResourceLocation)}.
*
* @param item the item to get the stone variant of
* @return the stone variant of the item
*/
private String computeStoneVariant(ResourceLocation item) {
for (String stoneVariant : stoneVariants) {
if (item.getPath().contains(stoneVariant + "_") || item.getPath().endsWith("_" + stoneVariant)) {
return stoneVariant.equals("stone") ? "" : stoneVariant;
}
}
return "";
}
}

View file

@ -0,0 +1,246 @@
package com.almostreliable.unified.unification;
import com.almostreliable.unified.AlmostUnifiedCommon;
import com.almostreliable.unified.api.unification.UnificationEntry;
import com.almostreliable.unified.api.unification.UnificationLookup;
import com.almostreliable.unified.utils.Utils;
import com.almostreliable.unified.utils.VanillaTagWrapper;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import javax.annotation.Nullable;
import java.util.*;
import java.util.regex.Pattern;
public class TagInheritance {
private final Options<Item> itemOptions;
private final Options<Block> blockOptions;
public TagInheritance(Mode itemMode, Map<TagKey<Item>, Set<Pattern>> itemInheritance, Mode blockMode, Map<TagKey<Block>, Set<Pattern>> blockInheritance) {
itemOptions = new Options<>(itemMode, itemInheritance);
blockOptions = new Options<>(blockMode, blockInheritance);
}
public boolean apply(VanillaTagWrapper<Item> itemTags, VanillaTagWrapper<Block> blockTags, List<? extends UnificationLookup> unificationLookups) {
Multimap<UnificationEntry<Item>, ResourceLocation> changedItemTags = HashMultimap.create();
Multimap<UnificationEntry<Item>, ResourceLocation> changedBlockTags = HashMultimap.create();
var relations = resolveRelations(unificationLookups);
if (relations.isEmpty()) return false;
for (var relation : relations) {
var targetItem = relation.targetItem;
var targetItemHolder = targetItem.asHolderOrThrow();
var targetBlockHolder = findTargetBlockHolder(blockTags, targetItem);
var targetItemTags = itemTags
.getTags(targetItem)
.stream()
.map(rl -> TagKey.create(Registries.ITEM, rl))
.collect(ImmutableSet.toImmutableSet());
for (var item : relation.items) {
var appliedItemTags = applyItemTags(itemTags, targetItemHolder, targetItemTags, item);
changedItemTags.putAll(targetItem, appliedItemTags);
if (targetBlockHolder != null) {
var appliedBlockTags = applyBlockTags(blockTags, targetBlockHolder, targetItemTags, item);
changedBlockTags.putAll(targetItem, appliedBlockTags);
}
}
}
if (!changedBlockTags.isEmpty()) {
changedBlockTags.asMap().forEach((target, tags) -> {
AlmostUnifiedCommon.LOGGER.info("[TagInheritance] Added '{}' to block tags {}", target.id(), tags);
});
}
if (!changedItemTags.isEmpty()) {
changedItemTags.asMap().forEach((target, tags) -> {
AlmostUnifiedCommon.LOGGER.info("[TagInheritance] Added '{}' to item tags {}", target.id(), tags);
});
return true;
}
return false;
}
@Nullable
private Holder<Block> findTargetBlockHolder(VanillaTagWrapper<Block> tagMap, UnificationEntry<Item> targetItem) {
var blockTags = tagMap.getTags(targetItem.id());
if (blockTags.isEmpty()) return null;
return BuiltInRegistries.BLOCK.getHolderOrThrow(ResourceKey.create(Registries.BLOCK, targetItem.id()));
}
private Set<ResourceLocation> applyItemTags(VanillaTagWrapper<Item> vanillaTags, Holder<Item> targetItem, Set<TagKey<Item>> targetItemTags, UnificationEntry<Item> item) {
var itemTags = vanillaTags.getTags(item);
Set<ResourceLocation> changed = new HashSet<>();
for (var itemTag : itemTags) {
var tag = TagKey.create(Registries.ITEM, itemTag);
if (itemOptions.shouldInherit(tag, targetItemTags) && addToVanilla(targetItem, tag, vanillaTags)) {
changed.add(itemTag);
}
}
return changed;
}
private Set<ResourceLocation> applyBlockTags(VanillaTagWrapper<Block> blockTagMap, Holder<Block> targetBlock, Set<TagKey<Item>> targetItemTags, UnificationEntry<Item> item) {
var blockTags = blockTagMap.getTags(item.id());
Set<ResourceLocation> changed = new HashSet<>();
for (var blockTag : blockTags) {
var tag = TagKey.create(Registries.BLOCK, blockTag);
if (blockOptions.shouldInherit(tag, targetItemTags) && addToVanilla(targetBlock, tag, blockTagMap)) {
changed.add(blockTag);
}
}
return changed;
}
/**
* Add given holder to the given tag.
*
* @param holder The holder
* @param tag The tag the holder should be added to
* @param vanillaTags The vanilla tag wrapper
* @return true if the holder was added, false if it was already present
*/
private static <T> boolean addToVanilla(Holder<T> holder, TagKey<T> tag, VanillaTagWrapper<T> vanillaTags) {
var tagHolders = vanillaTags.get(tag);
if (tagHolders.contains(holder)) return false; // already present, no need to add it again
vanillaTags.add(tag.location(), holder);
return true;
}
private Set<TagRelation> resolveRelations(Collection<? extends UnificationLookup> unificationLookups) {
Set<TagRelation> relations = new HashSet<>();
for (var unificationLookup : unificationLookups) {
for (TagKey<Item> unifyTag : unificationLookup.getTags()) {
if (itemOptions.skipForInheritance(unifyTag) && blockOptions.skipForInheritance(unifyTag)) {
continue;
}
var itemsByTag = unificationLookup.getTagEntries(unifyTag);
// avoid handling single entries and tags that only contain the same namespace for all items
if (Utils.allSameNamespace(itemsByTag)) continue;
var target = unificationLookup.getTagTargetItem(unifyTag);
if (target == null) continue;
var items = removeTargetItem(itemsByTag, target);
if (items.isEmpty()) continue;
relations.add(new TagRelation(unifyTag, target, items));
}
}
return relations;
}
/**
* Returns a set of all items that are not the target item and are valid by checking if they are registered.
*
* @param holders The set of all items that are in the tag
* @param target The target item
* @return A set of all items that are not the target item and are valid
*/
private Set<UnificationEntry<Item>> removeTargetItem(Collection<UnificationEntry<Item>> holders, UnificationEntry<Item> target) {
Set<UnificationEntry<Item>> result = new HashSet<>(holders.size());
for (var holder : holders) {
if (!holder.equals(target)) {
result.add(holder);
}
}
return result;
}
private record TagRelation(TagKey<Item> tag, UnificationEntry<Item> targetItem,
Set<UnificationEntry<Item>> items) {}
public enum Mode {
ALLOW,
DENY
}
private record Options<T>(Mode mode, Map<TagKey<T>, Set<Pattern>> inheritance) {
/**
* Checks if given tag is used in the inheritance config.
* <p>
* If mode is allowed, the tag should match any pattern in the config. If mode is deny, the tag should not match
* any pattern in the config.
*
* @param tag The tag to check
* @return True if the tag should be skipped
*/
public boolean skipForInheritance(TagKey<Item> tag) {
var tagStr = tag.location().toString();
boolean modeResult = mode == Mode.ALLOW;
for (Set<Pattern> patterns : inheritance.values()) {
for (Pattern pattern : patterns) {
if (pattern.matcher(tagStr).matches()) {
return !modeResult;
}
}
}
return modeResult;
}
/**
* Checks if given inheritance tag would match any of the target item tags.
* <p>
* E. g based on a simple config:
* <pre>
* {@code {
* "minecraft:beacon_payment_items": [
* "c:ores/silver"
* ]
* }}
* </pre>
* "minecraft:beacon_payment_items" would be the inheritance tag and "c:ores/silver" would be one of the target item tags.
* If mode is {@code DENY}, the check would be inverted.
*
* @param inheritanceTag The inheritance tag
* @param targetItemTags The target item tags
* @return True if we should allow the inheritance or false if we should deny the inheritance
*/
public boolean shouldInherit(TagKey<T> inheritanceTag, Collection<TagKey<Item>> targetItemTags) {
var patterns = inheritance.getOrDefault(inheritanceTag, Set.of());
boolean result = checkPatterns(targetItemTags, patterns);
// noinspection SimplifiableConditionalExpression
return mode == Mode.ALLOW ? result : !result;
}
private boolean checkPatterns(Collection<TagKey<Item>> tags, Collection<Pattern> patterns) {
for (var pattern : patterns) {
for (var tag : tags) {
if (pattern.matcher(tag.location().toString()).matches()) {
return true;
}
}
}
return false;
}
}
}

Some files were not shown because too many files have changed in this diff Show more