Recipe Modification Indicator (#8)

Co-authored-by: LLytho <main@lytho.dev>
This commit is contained in:
Relentless 2022-09-24 20:05:47 +02:00
parent 5e7789795b
commit a80405d726
No known key found for this signature in database
GPG key ID: 759D97B8C6F25265
30 changed files with 940 additions and 291 deletions

View file

@ -582,7 +582,7 @@ ij_json_keep_indents_on_empty_lines = false
ij_json_keep_line_breaks = true ij_json_keep_line_breaks = true
ij_json_space_after_colon = true ij_json_space_after_colon = true
ij_json_space_after_comma = true ij_json_space_after_comma = true
ij_json_space_before_colon = true ij_json_space_before_colon = false
ij_json_space_before_comma = false ij_json_space_before_comma = false
ij_json_spaces_within_braces = false ij_json_spaces_within_braces = false
ij_json_spaces_within_brackets = false ij_json_spaces_within_brackets = false

View file

@ -1,60 +1,57 @@
@file:Suppress("UnstableApiUsage")
val junitVersion: String by project
val minecraftVersion: String by project
val fabricLoaderVersion: String by project
val jeiVersion: String by project
val kubejsVersion: String by project
val mappingsChannel: String by project
val mappingsVersion: String by project
val modId: String by project
val modName: String by project
val baseArchiveName = "$modId-common-$minecraftVersion"
plugins { plugins {
`maven-publish`
id("fabric-loom") version "0.12-SNAPSHOT" id("fabric-loom") version "0.12-SNAPSHOT"
id("com.github.gmazzo.buildconfig") version "3.0.3" id("com.github.gmazzo.buildconfig") version "3.0.3"
} }
val minecraftVersion: String by project
val modName: String by project
val modId: String by project
val fabricLoaderVersion: String by project
val mappingsChannel: String by project
val mappingsVersion: String by project
val kubejsVersion: String by project
val jeiVersion: String by project
val baseArchiveName = "${modName}-common-${minecraftVersion}"
base { base {
archivesName.set(baseArchiveName) archivesName.set(baseArchiveName)
} }
loom { loom {
remapArchives.set(false); shareCaches()
remapArchives.set(false)
setupRemappedVariants.set(false); setupRemappedVariants.set(false);
runConfigs.configureEach { runConfigs.configureEach {
ideConfigGenerated(false) ideConfigGenerated(false)
} }
mixin {
useLegacyMixinAp.set(false)
}
} }
dependencies { dependencies {
minecraft("com.mojang:minecraft:${minecraftVersion}") minecraft("com.mojang:minecraft:$minecraftVersion")
modCompileOnly("net.fabricmc:fabric-loader:$fabricLoaderVersion")
mappings(loom.layered { mappings(loom.layered {
officialMojangMappings() officialMojangMappings()
// TODO: change this when updating to 1.19.2 // TODO: change this when updating to 1.19.2
parchment("org.parchmentmc.data:${mappingsChannel}-${minecraftVersion}.2:${mappingsVersion}@zip") parchment("org.parchmentmc.data:$mappingsChannel-$minecraftVersion.2:$mappingsVersion@zip")
}) })
implementation("com.google.code.findbugs:jsr305:3.0.2")
modApi("dev.latvian.mods:kubejs-fabric:${kubejsVersion}") modCompileOnly("mezz.jei:jei-$minecraftVersion-common:$jeiVersion") // required for common jei plugin and mixin
modCompileOnlyApi("mezz.jei:jei-${minecraftVersion}-common-api:${jeiVersion}") modCompileOnly("dev.latvian.mods:kubejs:$kubejsVersion") // required for common kubejs plugin
/** // JUnit Tests
* DON'T USE THIS! NEEDED TO COMPILE THIS PROJECT testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
*/ testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
modCompileOnly("net.fabricmc:fabric-loader:${fabricLoaderVersion}")
/**
* Test dependencies
*/
testImplementation ("org.junit.jupiter:junit-jupiter-api:5.8.1")
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.1")
} }
tasks { tasks {
withType<Test> { // TODO: test if this is necessary
useJUnitPlatform()
}
processResources { processResources {
val buildProps = project.properties val buildProps = project.properties
@ -62,13 +59,15 @@ tasks {
expand(buildProps) expand(buildProps)
} }
} }
withType<Test> {
useJUnitPlatform()
}
} }
buildConfig { buildConfig {
buildConfigField("String", "MOD_ID", "\"${modId}\"") buildConfigField("String", "MOD_ID", "\"${modId}\"")
buildConfigField("String", "MOD_VERSION", "\"${project.version}\"") buildConfigField("String", "MOD_VERSION", "\"${project.version}\"")
buildConfigField("String", "MOD_NAME", "\"${modName}\"") buildConfigField("String", "MOD_NAME", "\"${modName}\"")
packageName(project.group as String) packageName(project.group as String)
} }

View file

@ -30,6 +30,8 @@ public interface AlmostUnifiedPlatform {
*/ */
boolean isDevelopmentEnvironment(); boolean isDevelopmentEnvironment();
boolean isClient();
Path getConfigPath(); Path getConfigPath();
Path getLogPath(); Path getLogPath();

View file

@ -1,9 +1,6 @@
package com.almostreliable.unified.compat; package com.almostreliable.unified.compat;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.BuildConfig; import com.almostreliable.unified.BuildConfig;
import com.almostreliable.unified.Platform;
import com.almostreliable.unified.config.Config; import com.almostreliable.unified.config.Config;
import com.almostreliable.unified.config.UnifyConfig; import com.almostreliable.unified.config.UnifyConfig;
import mezz.jei.api.IModPlugin; import mezz.jei.api.IModPlugin;
@ -25,11 +22,6 @@ public class AlmostJEI implements IModPlugin {
@Override @Override
public void onRuntimeAvailable(IJeiRuntime jei) { public void onRuntimeAvailable(IJeiRuntime jei) {
var auPlatform = AlmostUnifiedPlatform.INSTANCE;
if (auPlatform.getPlatform() == Platform.FABRIC && auPlatform.isModLoaded("roughlyenoughitems")) {
return;
}
UnifyConfig config = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer()); UnifyConfig config = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer());
if (config.reiOrJeiDisabled()) { if (config.reiOrJeiDisabled()) {
return; return;

View file

@ -0,0 +1,64 @@
package com.almostreliable.unified.compat;
import com.almostreliable.unified.recipe.ClientRecipeTracker.ClientRecipeLink;
import com.almostreliable.unified.utils.Utils;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiComponent;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import java.util.List;
public final class RecipeIndicator {
public static final int SIZE = 16;
private static final ResourceLocation TEXTURE = Utils.getRL("textures/ingot.png");
private RecipeIndicator() {}
public static List<Component> constructTooltip(ClientRecipeLink link) {
var unified = Component.translatable(Utils.prefix("unified")).append(": ")
.withStyle(c -> c.withColor(ChatFormatting.AQUA));
unified.append(Component.translatable(Utils.prefix(link.isUnified() ? "yes" : "no"))
.withStyle(c -> c.withColor(ChatFormatting.WHITE)));
var duplicate = Component.translatable(Utils.prefix("duplicate")).append(": ")
.withStyle(c -> c.withColor(ChatFormatting.AQUA));
duplicate.append(Component.translatable(Utils.prefix(link.isDuplicate() ? "yes" : "no"))
.withStyle(c -> c.withColor(ChatFormatting.WHITE)));
return List.of(
Component.translatable(Utils.prefix("description")).withStyle(c -> c.withColor(ChatFormatting.GOLD)),
Component.literal(""),
unified,
duplicate,
Component.literal(""),
Component.translatable(Utils.prefix("warning")).withStyle(c -> c.withColor(ChatFormatting.RED))
);
}
public static void renderIndicator(PoseStack poseStack, Rect2i area) {
poseStack.pushPose();
poseStack.translate(area.getX(), area.getY(), 0);
var scale = area.getWidth() / (float) SIZE;
poseStack.scale(scale, scale, scale);
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderTexture(0, TEXTURE);
GuiComponent.blit(poseStack, 0, 0, 0, 0, SIZE, SIZE, SIZE, SIZE);
poseStack.popPose();
}
public static void renderTooltip(PoseStack poseStack, Rect2i area, int mX, int mY, ClientRecipeLink link) {
var screen = Minecraft.getInstance().screen;
if (screen == null) return;
if (mX >= area.getX() && mX <= area.getX() + area.getWidth() &&
mY >= area.getY() && mY <= area.getY() + area.getHeight()) {
screen.renderComponentTooltip(poseStack, constructTooltip(link), mX, mY);
}
}
}

View file

@ -0,0 +1,54 @@
package com.almostreliable.unified.mixin;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.google.common.collect.ImmutableMap;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BooleanSupplier;
@SuppressWarnings("unused")
public class AlmostMixinPlugin implements IMixinConfigPlugin {
private static final BooleanSupplier TRUE = () -> true;
private static final Map<String, BooleanSupplier> CONDITIONS = ImmutableMap.of(
"com.almostreliable.unified.mixin.JeiRecipeLayoutMixin", modLoaded("jei")
);
private static BooleanSupplier modLoaded(String id) {
return () -> AlmostUnifiedPlatform.INSTANCE.isModLoaded(id);
}
@Override
public void onLoad(String mixinPackage) {}
@SuppressWarnings("ReturnOfNull")
@Override
public String getRefMapperConfig() {
return null;
}
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return CONDITIONS.getOrDefault(mixinClassName, TRUE).getAsBoolean();
}
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {}
@SuppressWarnings("ReturnOfNull")
@Override
public List<String> getMixins() {
return null;
}
@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {}
@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {}
}

View file

@ -0,0 +1,42 @@
package com.almostreliable.unified.mixin;
import com.almostreliable.unified.compat.RecipeIndicator;
import com.almostreliable.unified.recipe.CRTLookup;
import com.mojang.blaze3d.vertex.PoseStack;
import mezz.jei.api.gui.drawable.IDrawable;
import mezz.jei.api.recipe.category.IRecipeCategory;
import mezz.jei.common.gui.recipes.layout.RecipeLayout;
import net.minecraft.client.renderer.Rect2i;
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;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(RecipeLayout.class)
public abstract class JeiRecipeLayoutMixin<R> {
@Shadow(remap = false) @Final
private static int RECIPE_BORDER_PADDING;
@Shadow(remap = false) @Final
private IRecipeCategory<R> recipeCategory;
@Shadow(remap = false) @Final
private R recipe;
@Inject(method = "drawRecipe", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;pushPose()V", ordinal = 1), locals = LocalCapture.CAPTURE_FAILHARD)
private void unified$drawRecipe(PoseStack stack, int mouseX, int mouseY, CallbackInfo ci, IDrawable background, int mX, int mY, IDrawable categoryBackground, int x, int y) {
var recipeId = recipeCategory.getRegistryName(recipe);
if (recipeId == null) return;
var link = CRTLookup.getLink(recipeId);
if (link == null) return;
var posX = x - RecipeIndicator.SIZE / 2 - RECIPE_BORDER_PADDING + 1;
var posY = y - RecipeIndicator.SIZE / 2 - RECIPE_BORDER_PADDING + 1;
var area = new Rect2i(posX, posY, 10, 10);
RecipeIndicator.renderIndicator(stack, area);
RecipeIndicator.renderTooltip(stack, area, mX, mY, link);
}
}

View file

@ -0,0 +1,28 @@
package com.almostreliable.unified.recipe;
import com.almostreliable.unified.BuildConfig;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
public final class CRTLookup {
private CRTLookup() {}
@Nullable
public static ClientRecipeTracker.ClientRecipeLink getLink(ResourceLocation recipeId) {
ResourceLocation linkRecipe = new ResourceLocation(BuildConfig.MOD_ID, recipeId.getNamespace());
if (Minecraft.getInstance().level == null) {
return null;
}
return Minecraft.getInstance().level
.getRecipeManager()
.byKey(linkRecipe)
.filter(ClientRecipeTracker.class::isInstance)
.map(ClientRecipeTracker.class::cast)
.map(tracker -> tracker.getLink(recipeId))
.orElse(null);
}
}

View file

@ -0,0 +1,231 @@
package com.almostreliable.unified.recipe;
import com.almostreliable.unified.BuildConfig;
import com.almostreliable.unified.utils.Utils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.network.FriendlyByteBuf;
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.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.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 static final ResourceLocation ID = Utils.getRL("client_recipe_tracker");
public static final String RECIPES = "recipes";
public static final String NAMESPACE = "namespace";
public static final int UNIFIED_FLAG = 1;
public static final int DUPLICATE_FLAG = 2;
public static final RecipeSerializer<ClientRecipeTracker> SERIALIZER = new Serializer();
public static final RecipeType<ClientRecipeTracker> TYPE = new RecipeType<>() {
@Override
public String toString() {
return ID.getPath();
}
};
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.
*
* @param isUnified Whether the recipe was unified.
* @param isDuplicate Whether the recipe had duplicates.
* @param idPath The path of the recipe.
* @return String representation as: `flag$idPath`
*/
private static String createRaw(boolean isUnified, boolean isDuplicate, String idPath) {
int flag = 0;
if (isUnified) flag |= UNIFIED_FLAG;
if (isDuplicate) flag |= DUPLICATE_FLAG;
return flag + "$" + idPath;
}
//<editor-fold defaultstate="collapsed" desc="Default recipe stuff. Ignore this. Forget this.">
@Override
public boolean matches(Container container, Level level) {
return false;
}
@Override
public ItemStack assemble(Container container) {
return ItemStack.EMPTY;
}
@Override
public boolean canCraftInDimensions(int width, int height) {
return false;
}
@Override
public ItemStack getResultItem() {
return ItemStack.EMPTY;
}
@Override
public ResourceLocation getId() {
return id;
}
//</editor-fold>
@Override
public RecipeSerializer<?> getSerializer() {
return SERIALIZER;
}
@Override
public RecipeType<?> getType() {
return TYPE;
}
private void add(ClientRecipeLink clientRecipeLink) {
recipes.put(clientRecipeLink.id(), clientRecipeLink);
}
@Nullable
public ClientRecipeLink getLink(ResourceLocation recipeId) {
return recipes.get(recipeId);
}
public record ClientRecipeLink(ResourceLocation id, boolean isUnified, boolean isDuplicate) {}
public static class Serializer implements RecipeSerializer<ClientRecipeTracker> {
/**
* Reads a recipe from a json file. Recipe will look like this:
* <pre>
* {@code
* {
* "type": "almostunified:client_recipe_tracker",
* "namespace": "minecraft", // The namespace of the recipes.
* "recipes": [
* "flag$recipePath",
* "flag$recipe2Path",
* ...
* "flag$recipeNPath"
* ]
* }
* }
* </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;
}
@Override
public ClientRecipeTracker fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) {
int size = buffer.readInt();
String namespace = buffer.readUtf();
ClientRecipeTracker recipe = new ClientRecipeTracker(recipeId, namespace);
for (int i = 0; i < size; i++) {
String raw = buffer.readUtf();
ClientRecipeLink clientRecipeLink = parseRaw(namespace, raw);
recipe.add(clientRecipeLink);
}
return recipe;
}
/**
* Writes the tracker to the buffer. The namespace is written separately to save some bytes.
* Buffer output will look like:
* <pre>
* size
* namespace
* flag$recipePath
* flag$recipe2Path
* ...
* flag$recipeNPath
* </pre>
*
* @param buffer The buffer to write to
* @param recipe The recipe to write
*/
@Override
public void toNetwork(FriendlyByteBuf buffer, ClientRecipeTracker recipe) {
buffer.writeInt(recipe.recipes.size());
buffer.writeUtf(recipe.namespace);
for (ClientRecipeLink clientRecipeLink : recipe.recipes.values()) {
String raw = createRaw(clientRecipeLink.isUnified(),
clientRecipeLink.isDuplicate(),
clientRecipeLink.id().getPath());
buffer.writeUtf(raw);
}
}
/**
* Creates a {@link ClientRecipeLink} from a raw string for the given namespace.
*
* @param namespace The namespace to use.
* @param raw The raw string.
* @return The client sided recipe link.
*/
private 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);
}
}
public static class RawBuilder {
private final Map<String, JsonArray> recipesByNamespace = new HashMap<>();
public void add(RecipeLink recipe) {
ResourceLocation recipeId = recipe.getId();
JsonArray array = recipesByNamespace.computeIfAbsent(recipeId.getNamespace(), k -> new JsonArray());
array.add(createRaw(recipe.isUnified(), recipe.hasDuplicateLink(), recipeId.getPath()));
}
/**
* Creates a map with the namespace as key and the json recipe.
* These recipes are used later in {@link Serializer#fromJson(ResourceLocation, JsonObject)}
*
* @return The map with the namespace as key and the json recipe.
*/
public Map<ResourceLocation, JsonObject> compute() {
Map<ResourceLocation, JsonObject> result = new HashMap<>();
recipesByNamespace.forEach((namespace, recipes) -> {
JsonObject json = new JsonObject();
json.addProperty("type", ID.toString());
json.addProperty(NAMESPACE, namespace);
json.add(RECIPES, recipes);
result.put(new ResourceLocation(BuildConfig.MOD_ID, namespace), json);
});
return result;
}
}
}

View file

@ -74,7 +74,7 @@ public class RecipeDumper {
.stream() .stream()
.sorted(Comparator.comparing(r -> r.getId().toString())) .sorted(Comparator.comparing(r -> r.getId().toString()))
.map(r -> "\t\t- " + r.getId() + "\n") .map(r -> "\t\t- " + r.getId() + "\n")
.collect(Collectors.joining("", String.format("\t%s\n", link.getMaster().getDumpInfo()), "\n")); .collect(Collectors.joining("", String.format("\t%s\n", link.getMaster().getId().toString()), "\n"));
} }
private void dumpOverview(StringBuilder stringBuilder) { private void dumpOverview(StringBuilder stringBuilder) {
@ -139,7 +139,7 @@ public class RecipeDumper {
getSortedUnifiedRecipes(type).forEach(recipe -> { getSortedUnifiedRecipes(type).forEach(recipe -> {
stringBuilder stringBuilder
.append("\t- ") .append("\t- ")
.append(recipe.getDumpInfo()) .append(recipe.getId().toString())
.append("\n") .append("\n")
.append("\t\t Original: ") .append("\t\t Original: ")
.append(recipe.getOriginal().toString()) .append(recipe.getOriginal().toString())

View file

@ -1,6 +1,5 @@
package com.almostreliable.unified.recipe; package com.almostreliable.unified.recipe;
import com.almostreliable.unified.BuildConfig;
import com.almostreliable.unified.utils.JsonCompare; import com.almostreliable.unified.utils.JsonCompare;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -10,6 +9,7 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
public class RecipeLink { public class RecipeLink {
private final ResourceLocation id; private final ResourceLocation id;
@ -165,23 +165,6 @@ public class RecipeLink {
return getUnified() != null ? getUnified() : getOriginal(); return getUnified() != null ? getUnified() : getOriginal();
} }
public ResourceLocation createNewRecipeId() {
StringBuilder sb = new StringBuilder();
if (isUnified()) sb.append("u");
if (hasDuplicateLink()) sb.append("d");
if (!sb.isEmpty()) sb.append("/");
sb.append(getId().getNamespace()).append("/").append(getId().getPath());
return new ResourceLocation(BuildConfig.MOD_ID, sb.toString());
}
public String getDumpInfo() {
if (isUnified() || hasDuplicateLink()) {
return getId() + " (Renamed to: " + createNewRecipeId() + ")";
}
return getId().toString();
}
public static final class DuplicateLink { public static final class DuplicateLink {
private final Set<RecipeLink> recipes = new HashSet<>(); private final Set<RecipeLink> recipes = new HashSet<>();
private RecipeLink currentMaster; private RecipeLink currentMaster;
@ -208,6 +191,10 @@ public class RecipeLink {
return Collections.unmodifiableSet(recipes); return Collections.unmodifiableSet(recipes);
} }
public Set<RecipeLink> getRecipesWithoutMaster() {
return recipes.stream().filter(recipe -> recipe != currentMaster).collect(Collectors.toSet());
}
@Override @Override
public String toString() { public String toString() {
return "Link{currentMaster=" + currentMaster + ", recipes=" + recipes.size() + "}"; return "Link{currentMaster=" + currentMaster + ", recipes=" + recipes.size() + "}";

View file

@ -17,7 +17,6 @@ import com.google.gson.JsonPrimitive;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -37,7 +36,7 @@ public class RecipeTransformer {
this.duplicationConfig = duplicationConfig; this.duplicationConfig = duplicationConfig;
} }
public boolean hasValidType(JsonObject json) { public boolean hasValidRecipeType(JsonObject json) {
if (json.get("type") instanceof JsonPrimitive primitive) { if (json.get("type") instanceof JsonPrimitive primitive) {
ResourceLocation type = ResourceLocation.tryParse(primitive.getAsString()); ResourceLocation type = ResourceLocation.tryParse(primitive.getAsString());
return type != null && unifyConfig.includeRecipeType(type); return type != null && unifyConfig.includeRecipeType(type);
@ -55,6 +54,7 @@ public class RecipeTransformer {
public Result transformRecipes(Map<ResourceLocation, JsonElement> recipes) { public Result transformRecipes(Map<ResourceLocation, JsonElement> recipes) {
AlmostUnified.LOG.warn("Recipe count: " + recipes.size()); AlmostUnified.LOG.warn("Recipe count: " + recipes.size());
ClientRecipeTracker.RawBuilder tracker = new ClientRecipeTracker.RawBuilder();
Result result = new Result(); Result result = new Result();
Map<ResourceLocation, List<RecipeLink>> byType = groupRecipesByType(recipes); Map<ResourceLocation, List<RecipeLink>> byType = groupRecipesByType(recipes);
@ -65,12 +65,14 @@ public class RecipeTransformer {
recipeLink.getId(), recipeLink.getId(),
json))); json)));
} else { } else {
transformRecipes(recipeLinks, recipes::put, recipes::remove); transformRecipes(recipeLinks, recipes, tracker);
} }
result.addAll(recipeLinks); result.addAll(recipeLinks);
}); });
AlmostUnified.LOG.warn("Recipe count afterwards: " + recipes.size()); AlmostUnified.LOG.warn("Recipe count afterwards: " + recipes.size());
Map<ResourceLocation, JsonObject> compute = tracker.compute();
recipes.putAll(compute);
return result; return result;
} }
@ -100,34 +102,26 @@ public class RecipeTransformer {
return Optional.empty(); return Optional.empty();
} }
private void transformRecipes(List<RecipeLink> recipeLinks, BiConsumer<ResourceLocation, JsonElement> onAdd, Consumer<ResourceLocation> onRemove) { /**
Set<RecipeLink.DuplicateLink> duplicates = new HashSet<>(recipeLinks.size()); * Transforms a list of recipes. Part of the transformation is to unify recipes with the given {@link ReplacementMap}.
List<RecipeLink> unified = new ArrayList<>(recipeLinks.size()); * After unification, recipes will be checked for duplicates.
for (RecipeLink curRecipe : recipeLinks) { * All duplicates will be removed from <b>{@code Map<ResourceLocation, JsonElement> allRecipes}</b>,
unifyRecipe(curRecipe); * while unified recipes will replace the original recipes in the map.
if (curRecipe.isUnified()) { * <p>
onAdd.accept(curRecipe.getId(), curRecipe.getUnified()); * This method will also add the recipes to the given {@link ClientRecipeTracker.RawBuilder}
unified.add(curRecipe); *
} * @param recipeLinks The list of recipes to transform.
} * @param allRecipes The map of all existing recipes.
* @param tracker The tracker to add the recipes to.
for (RecipeLink recipeLink : duplicationConfig.isStrictMode() ? recipeLinks : unified) { */
if (handleDuplicate(recipeLink, recipeLinks) && recipeLink.getDuplicateLink() != null) { private void transformRecipes(List<RecipeLink> recipeLinks, Map<ResourceLocation, JsonElement> allRecipes, ClientRecipeTracker.RawBuilder tracker) {
duplicates.add(recipeLink.getDuplicateLink()); var unified = unifyRecipes(recipeLinks, (r) -> allRecipes.put(r.getId(), r.getUnified()));
} var duplicates = handleDuplicates(duplicationConfig.isStrictMode() ? recipeLinks : unified, recipeLinks);
} duplicates
.stream()
for (RecipeLink.DuplicateLink link : duplicates) { .flatMap(d -> d.getRecipesWithoutMaster().stream())
link.getRecipes().forEach(recipe -> { .forEach(r -> allRecipes.remove(r.getId()));
onRemove.accept(recipe.getId()); unified.forEach(tracker::add);
unified.remove(recipe);
});
onAdd.accept(link.getMaster().createNewRecipeId(), link.getMaster().getActual());
}
for (RecipeLink link : unified) {
onRemove.accept(link.getId());
onAdd.accept(link.createNewRecipeId(), link.getActual());
}
} }
public Map<ResourceLocation, List<RecipeLink>> groupRecipesByType(Map<ResourceLocation, JsonElement> recipes) { public Map<ResourceLocation, List<RecipeLink>> groupRecipesByType(Map<ResourceLocation, JsonElement> recipes) {
@ -139,8 +133,32 @@ public class RecipeTransformer {
.collect(Collectors.groupingByConcurrent(RecipeLink::getType)); .collect(Collectors.groupingByConcurrent(RecipeLink::getType));
} }
/**
* Checks if a recipe should be included in the transformation.
*
* @param recipe The recipe to check.
* @param json The recipe's json. Will be used to check if the recipe has a valid type.
* @return True if the recipe should be included, false otherwise.
*/
private boolean includeRecipe(ResourceLocation recipe, JsonElement json) { private boolean includeRecipe(ResourceLocation recipe, JsonElement json) {
return unifyConfig.includeRecipe(recipe) && json.isJsonObject() && hasValidType(json.getAsJsonObject()); return unifyConfig.includeRecipe(recipe) && json.isJsonObject() && hasValidRecipeType(json.getAsJsonObject());
}
/**
* Compares a list of recipes against another list for duplicates.
*
* @param recipeLinks The list of recipes
* @param linksToCompare The list of recipes to compare against
* @return A list of {@link RecipeLink.DuplicateLink}s containing all duplicates.
*/
private Set<RecipeLink.DuplicateLink> handleDuplicates(Collection<RecipeLink> recipeLinks, List<RecipeLink> linksToCompare) {
Set<RecipeLink.DuplicateLink> duplicates = new HashSet<>(recipeLinks.size());
for (RecipeLink recipeLink : recipeLinks) {
if (handleDuplicate(recipeLink, linksToCompare) && recipeLink.getDuplicateLink() != null) {
duplicates.add(recipeLink.getDuplicateLink());
}
}
return duplicates;
} }
private boolean handleDuplicate(RecipeLink curRecipe, List<RecipeLink> recipes) { private boolean handleDuplicate(RecipeLink curRecipe, List<RecipeLink> recipes) {
@ -166,6 +184,25 @@ public class RecipeTransformer {
return foundDuplicate; return foundDuplicate;
} }
/**
* Unifies a list of recipes. On unification, {@code Consumer<RecipeLink>} will be called
*
* @param recipeLinks The list of recipes to unify.
* @param onUnified A consumer that will be called on each unified recipe.
* @return A list of unified recipes.
*/
private Set<RecipeLink> unifyRecipes(List<RecipeLink> recipeLinks, Consumer<RecipeLink> onUnified) {
Set<RecipeLink> unified = new HashSet<>(recipeLinks.size());
for (RecipeLink recipeLink : recipeLinks) {
unifyRecipe(recipeLink);
if (recipeLink.isUnified()) {
onUnified.accept(recipeLink);
unified.add(recipeLink);
}
}
return unified;
}
/** /**
* Unifies a single recipe link. This method will modify the recipe link in-place. * Unifies a single recipe link. This method will modify the recipe link in-place.
* {@link RecipeHandlerFactory} will apply multiple unification's onto the recipe. * {@link RecipeHandlerFactory} will apply multiple unification's onto the recipe.

View file

@ -24,4 +24,17 @@ public final class Utils {
return UnifyTag.item(rl); return UnifyTag.item(rl);
} }
@SuppressWarnings("unchecked")
public static <T> T cast(Object o) {
return (T) o;
}
public static ResourceLocation getRL(String path) {
return new ResourceLocation(BuildConfig.MOD_ID, path);
}
public static String prefix(String path) {
return BuildConfig.MOD_ID + "." + path;
}
} }

View file

@ -4,11 +4,13 @@
"package": "com.almostreliable.unified.mixin", "package": "com.almostreliable.unified.mixin",
"refmap": "almostunified.refmap.json", "refmap": "almostunified.refmap.json",
"compatibilityLevel": "JAVA_17", "compatibilityLevel": "JAVA_17",
"plugin": "com.almostreliable.unified.mixin.AlmostMixinPlugin",
"mixins": [ "mixins": [
"RecipeManagerMixin", "RecipeManagerMixin",
"ReloadableServerResourcesMixin" "ReloadableServerResourcesMixin"
], ],
"client": [ "client": [
"JeiRecipeLayoutMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1

View file

@ -0,0 +1,8 @@
{
"almostunified.description": "Modified by Almost Unified!",
"almostunified.warning": "Don't report the recipe to the original author.",
"almostunified.unified": "Unified",
"almostunified.duplicate": "Had Duplicates",
"almostunified.yes": "Yes",
"almostunified.no": "No"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

View file

@ -1,63 +1,31 @@
plugins { @file:Suppress("UnstableApiUsage")
idea
`maven-publish`
id("fabric-loom") version "0.12-SNAPSHOT"
}
val extraModsDirectory: String by project
val fabricRecipeViewer: String by project
val minecraftVersion: String by project val minecraftVersion: String by project
val fabricVersion: String by project val fabricVersion: String by project
val fabricLoaderVersion: String by project val fabricLoaderVersion: String by project
val modName: String by project
val modId: String by project
val mappingsChannel: String by project
val mappingsVersion: String by project
val extraModsDirectory: String by project
val reiVersion: String by project val reiVersion: String by project
val jeiVersion: String by project val jeiVersion: String by project
val kubejsVersion: String by project
val mappingsChannel: String by project
val mappingsVersion: String by project
val modId: String by project
val modName: String by project
val baseArchiveName = "${modId}-fabric-${minecraftVersion}" val baseArchiveName = "$modId-fabric-$minecraftVersion"
plugins {
id("fabric-loom") version "0.12-SNAPSHOT"
}
base { base {
archivesName.set(baseArchiveName) archivesName.set(baseArchiveName)
} }
dependencies {
minecraft("com.mojang:minecraft:${minecraftVersion}")
mappings(loom.layered {
officialMojangMappings()
// TODO: change this when updating to 1.19.2
parchment("org.parchmentmc.data:${mappingsChannel}-${minecraftVersion}.2:${mappingsVersion}@zip")
})
implementation("com.google.code.findbugs:jsr305:3.0.2")
modImplementation("net.fabricmc:fabric-loader:${fabricLoaderVersion}")
modApi("net.fabricmc.fabric-api:fabric-api:${fabricVersion}")
modImplementation("net.fabricmc.fabric-api:fabric-api:${fabricVersion}")
modCompileOnly("me.shedaniel:RoughlyEnoughItems-api-fabric:${reiVersion}")
modRuntimeOnly("me.shedaniel:RoughlyEnoughItems-fabric:${reiVersion}")
// required to run the fabric client
modRuntimeOnly("teamreborn:energy:2.2.0")
modCompileOnlyApi("mezz.jei:jei-${minecraftVersion}-common-api:${jeiVersion}")
modCompileOnlyApi("mezz.jei:jei-${minecraftVersion}-fabric-api:${jeiVersion}")
fileTree("$extraModsDirectory-$minecraftVersion") { include("**/*.jar") }
.forEach { f ->
val sepIndex = f.nameWithoutExtension.lastIndexOf('-');
if(sepIndex == -1) {
throw IllegalArgumentException("Invalid mod name: ${f.nameWithoutExtension}")
}
val mod = f.nameWithoutExtension.substring(0, sepIndex);
val version = f.nameWithoutExtension.substring(sepIndex + 1);
println("Extra mod $mod with version $version detected")
modLocalRuntime("$extraModsDirectory:$mod:$version")
}
implementation(project(":Common", "namedElements"))
}
loom { loom {
shareCaches()
runs { runs {
named("client") { named("client") {
client() client()
@ -76,19 +44,59 @@ loom {
} }
mixin { mixin {
defaultRefmapName.set("${modId}.refmap.json") defaultRefmapName.set("$modId.refmap.json")
}
}
dependencies {
implementation(project(":Common", "namedElements")) { isTransitive = false }
minecraft("com.mojang:minecraft:$minecraftVersion")
modImplementation("net.fabricmc:fabric-loader:$fabricLoaderVersion")
modImplementation("net.fabricmc.fabric-api:fabric-api:$fabricVersion")
mappings(loom.layered {
officialMojangMappings()
// TODO: change this when updating to 1.19.2
parchment("org.parchmentmc.data:$mappingsChannel-$minecraftVersion.2:$mappingsVersion@zip")
})
modCompileOnly("me.shedaniel:RoughlyEnoughItems-api-fabric:$reiVersion") // required for fabric rei plugin
modCompileOnly("mezz.jei:jei-$minecraftVersion-fabric:$jeiVersion") // required for common jei plugin and mixin
// runtime only
when (fabricRecipeViewer) {
"rei" -> modLocalRuntime("me.shedaniel:RoughlyEnoughItems-fabric:$reiVersion")
"jei" -> modLocalRuntime("mezz.jei:jei-$minecraftVersion-fabric:$jeiVersion")
else -> throw GradleException("Invalid fabricRecipeViewer value: $fabricRecipeViewer")
}
// required for common kubejs plugin and fabric runtime
modCompileOnly(modLocalRuntime("dev.latvian.mods:kubejs-fabric:$kubejsVersion")!!)
val extraMods = fileTree("$extraModsDirectory-$minecraftVersion") { include("**/*.jar") }
if (extraMods.files.isNotEmpty()) {
// required when running the fabric client with extra mods
modLocalRuntime("teamreborn:energy:2.2.0")
}
extraMods.forEach { f ->
val sepIndex = f.nameWithoutExtension.lastIndexOf('-')
if (sepIndex == -1) {
throw IllegalArgumentException("Invalid mod name: ${f.nameWithoutExtension}")
}
val mod = f.nameWithoutExtension.substring(0, sepIndex)
val version = f.nameWithoutExtension.substring(sepIndex + 1)
println("Extra mod $mod with version $version detected")
modLocalRuntime("$extraModsDirectory:$mod:$version")
} }
} }
tasks { tasks {
// TODO: test if this is necessary
jar { jar {
from("LICENSE") { from("LICENSE") {
rename { "${it}_${modName}" } rename { "${it}_${modName}" }
} }
} }
withType<JavaCompile> { // TODO: test if this is necessary
source(project(":Common").sourceSets.main.get().allSource)
}
processResources { processResources {
from(project(":Common").sourceSets.main.get().resources) from(project(":Common").sourceSets.main.get().resources)
inputs.property("version", project.version) inputs.property("version", project.version)
@ -97,6 +105,9 @@ tasks {
expand("version" to project.version) expand("version" to project.version)
} }
} }
withType<JavaCompile> {
source(project(":Common").sourceSets.main.get().allSource)
}
} }
publishing { publishing {

View file

@ -1,10 +1,14 @@
package com.almostreliable.unified; package com.almostreliable.unified;
import com.almostreliable.unified.recipe.ClientRecipeTracker;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.minecraft.core.Registry;
public class AlmostUnifiedFabric implements ModInitializer { public class AlmostUnifiedFabric implements ModInitializer {
@Override @Override
public void onInitialize() { public void onInitialize() {
Registry.register(Registry.RECIPE_SERIALIZER, ClientRecipeTracker.ID, ClientRecipeTracker.SERIALIZER);
Registry.register(Registry.RECIPE_TYPE, ClientRecipeTracker.ID, ClientRecipeTracker.TYPE);
} }
} }

View file

@ -1,6 +1,7 @@
package com.almostreliable.unified; package com.almostreliable.unified;
import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory; import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import java.nio.file.Path; import java.nio.file.Path;
@ -22,6 +23,11 @@ public class AlmostUnifiedPlatformFabric implements AlmostUnifiedPlatform {
return FabricLoader.getInstance().isDevelopmentEnvironment(); return FabricLoader.getInstance().isDevelopmentEnvironment();
} }
@Override
public boolean isClient() {
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT;
}
@Override @Override
public Path getConfigPath() { public Path getConfigPath() {
return FabricLoader.getInstance().getConfigDir().resolve(BuildConfig.MOD_ID); return FabricLoader.getInstance().getConfigDir().resolve(BuildConfig.MOD_ID);

View file

@ -1,24 +1,96 @@
package com.almostreliable.unified.compat; package com.almostreliable.unified.compat;
import com.almostreliable.unified.AlmostUnifiedPlatform;
import com.almostreliable.unified.config.Config; import com.almostreliable.unified.config.Config;
import com.almostreliable.unified.config.UnifyConfig; import com.almostreliable.unified.config.UnifyConfig;
import com.almostreliable.unified.recipe.CRTLookup;
import com.almostreliable.unified.recipe.ClientRecipeTracker.ClientRecipeLink;
import com.almostreliable.unified.utils.Utils;
import me.shedaniel.math.Rectangle;
import me.shedaniel.rei.api.client.gui.DisplayRenderer;
import me.shedaniel.rei.api.client.gui.widgets.Widget;
import me.shedaniel.rei.api.client.gui.widgets.Widgets;
import me.shedaniel.rei.api.client.plugins.REIClientPlugin; import me.shedaniel.rei.api.client.plugins.REIClientPlugin;
import me.shedaniel.rei.api.client.registry.category.ButtonArea;
import me.shedaniel.rei.api.client.registry.category.CategoryRegistry;
import me.shedaniel.rei.api.client.registry.category.extension.CategoryExtensionProvider;
import me.shedaniel.rei.api.client.registry.display.DisplayCategory;
import me.shedaniel.rei.api.client.registry.display.DisplayCategoryView;
import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; import me.shedaniel.rei.api.client.registry.entry.EntryRegistry;
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.EntryStacks; import me.shedaniel.rei.api.common.util.EntryStacks;
import net.minecraft.client.renderer.Rect2i;
import javax.annotation.Nullable;
import java.util.List;
@SuppressWarnings("UnstableApiUsage")
public class AlmostREI implements REIClientPlugin { public class AlmostREI implements REIClientPlugin {
@Override @Override
public void registerEntries(EntryRegistry registry) { public void registerEntries(EntryRegistry registry) {
if (AlmostUnifiedPlatform.INSTANCE.isModLoaded("jei")) {
return;
}
UnifyConfig config = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer()); UnifyConfig config = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer());
if(config.reiOrJeiDisabled()) { if (config.reiOrJeiDisabled()) return;
return;
}
HideHelper.createHidingList(config).stream().map(EntryStacks::of).forEach(registry::removeEntry); HideHelper.createHidingList(config).stream().map(EntryStacks::of).forEach(registry::removeEntry);
} }
@Override
public void postStage(PluginManager<REIClientPlugin> manager, ReloadStage stage) {
if (stage != ReloadStage.END || !manager.equals(PluginManager.getClientInstance())) return;
CategoryRegistry.getInstance().forEach(category -> {
IndicatorExtension extension = new IndicatorExtension(category.getPlusButtonArea().orElse(null));
category.registerExtension(Utils.cast(extension));
});
}
@SuppressWarnings("OverrideOnly")
private record IndicatorExtension(@Nullable ButtonArea plusButtonArea)
implements CategoryExtensionProvider<Display> {
@Override
public DisplayCategoryView<Display> provide(Display display, DisplayCategory<Display> category, DisplayCategoryView<Display> lastView) {
return display
.getDisplayLocation()
.map(CRTLookup::getLink)
.map(link -> (DisplayCategoryView<Display>) new IndicatorView(lastView, link))
.orElse(lastView);
}
private final class IndicatorView implements DisplayCategoryView<Display> {
private final DisplayCategoryView<Display> lastView;
private final ClientRecipeLink link;
private IndicatorView(DisplayCategoryView<Display> lastView, ClientRecipeLink link) {
this.lastView = lastView;
this.link = link;
}
@Override
public DisplayRenderer getDisplayRenderer(Display display) {
return lastView.getDisplayRenderer(display);
}
@Override
public List<Widget> setupDisplay(Display display, Rectangle bounds) {
var widgets = lastView.setupDisplay(display, bounds);
var area = calculateArea(bounds);
widgets.add(Widgets.createDrawableWidget((helper, stack, mX, mY, delta) ->
RecipeIndicator.renderIndicator(stack, area)));
var tooltipArea = new Rectangle(area.getX(), area.getY(), area.getWidth(), area.getHeight());
widgets.add(Widgets.createTooltip(tooltipArea, RecipeIndicator.constructTooltip(link)));
return widgets;
}
private Rect2i calculateArea(Rectangle bounds) {
if (plusButtonArea != null) {
var area = plusButtonArea.get(bounds);
return new Rect2i(area.x, area.y - area.height - 2, area.width, area.height);
}
return new Rect2i(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
}
} }

View file

@ -1,127 +1,127 @@
plugins { @file:Suppress("UnstableApiUsage")
java
eclipse
`maven-publish`
id("net.minecraftforge.gradle") version ("5.1.+")
id("org.parchmentmc.librarian.forgegradle") version ("1.+")
id("org.spongepowered.mixin") version ("0.7.+")
}
val junitVersion: String by project
val extraModsDirectory: String by project
val forgeRecipeViewer: String by project
val minecraftVersion: String by project val minecraftVersion: String by project
val mixinVersion: String by project
val forgeVersion: String by project val forgeVersion: String by project
val modId: String by project val reiVersion: String by project
val jeiVersion: String by project
val kubejsVersion: String by project
val mappingsChannel: String by project val mappingsChannel: String by project
val mappingsVersion: String by project val mappingsVersion: String by project
val extraModsDirectory: String by project val modId: String by project
val jeiVersion: String by project val modName: String by project
val baseArchiveName = "${modId}-forge-${minecraftVersion}" val baseArchiveName = "$modId-forge-$minecraftVersion"
val commonTests: SourceSetOutput = project(":Common").sourceSets["test"].output
plugins {
id("dev.architectury.loom") version "0.12.0-SNAPSHOT"
}
base { base {
archivesName.set(baseArchiveName) archivesName.set(baseArchiveName)
} }
minecraft { loom {
// TODO: change this when updating to 1.19.2 shareCaches()
mappings(mappingsChannel, "1.18.2-${mappingsVersion}-${minecraftVersion}") silentMojangMappingsLicense()
runs { runs {
create("client") { named("client") {
workingDirectory(project.file("run")) client()
ideaModule("${rootProject.name}.${project.name}.main") configName = "Forge Client"
taskName("Client") ideConfigGenerated(true)
property("mixin.env.remapRefMap", "true") runDir("run")
property("mixin.env.refMapRemappingFile", "${projectDir}/build/createSrgToMcp/output.srg") vmArgs("-XX:+IgnoreUnrecognizedVMOptions", "-XX:+AllowEnhancedClassRedefinition")
jvmArg("-XX:+IgnoreUnrecognizedVMOptions")
jvmArg("-XX:+AllowEnhancedClassRedefinition")
mods {
create(modId) {
source(sourceSets.main.get())
source(project(":Common").sourceSets.main.get())
} }
named("server") {
server()
configName = "Forge Server"
ideConfigGenerated(true)
runDir("run")
vmArgs("-XX:+IgnoreUnrecognizedVMOptions", "-XX:+AllowEnhancedClassRedefinition")
} }
} }
create("server") { forge {
workingDirectory(project.file("run")) mixinConfig("$modId-common.mixins.json")
ideaModule("${rootProject.name}.${project.name}.main")
taskName("Server")
property("mixin.env.remapRefMap", "true")
property("mixin.env.refMapRemappingFile", "${projectDir}/build/createSrgToMcp/output.srg")
jvmArg("-XX:+IgnoreUnrecognizedVMOptions")
jvmArg("-XX:+AllowEnhancedClassRedefinition")
mods {
create(modId) {
source(sourceSets.main.get())
source(project(":Common").sourceSets.main.get())
}
}
}
}
}
sourceSets.main.get().resources.srcDir("src/generated/resources")
// from millions of solutions, this is the only one which works... :-)
val commonTests: SourceSetOutput = project(":Common").sourceSets["test"].output
dependencies {
minecraft("net.minecraftforge:forge:${minecraftVersion}-${forgeVersion}")
compileOnly(project(":Common"))
compileOnly(fg.deobf("mezz.jei:jei-${minecraftVersion}-common-api:${jeiVersion}"))
compileOnly(fg.deobf("mezz.jei:jei-${minecraftVersion}-forge-api:${jeiVersion}"))
runtimeOnly(fg.deobf("mezz.jei:jei-${minecraftVersion}-forge:${jeiVersion}"))
fileTree("$extraModsDirectory-$minecraftVersion") { include("**/*.jar") }
.forEach { f ->
val sepIndex = f.nameWithoutExtension.lastIndexOf('-');
if(sepIndex == -1) {
throw IllegalArgumentException("Invalid mod name: ${f.nameWithoutExtension}")
}
val mod = f.nameWithoutExtension.substring(0, sepIndex);
val version = f.nameWithoutExtension.substring(sepIndex + 1);
println("Extra mod $mod with version $version detected")
runtimeOnly(fg.deobf("$extraModsDirectory:$mod:$version"))
}
annotationProcessor("org.spongepowered:mixin:${mixinVersion}:processor")
/**
* Test dependencies
*/
testImplementation(project(":Common"))
testImplementation(commonTests)
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
} }
mixin { mixin {
add(sourceSets.main.get(), "${modId}.refmap.json") defaultRefmapName.set("$modId.refmap.json")
config("${modId}-common.mixins.json") }
}
dependencies {
implementation(project(":Common", "namedElements")) { isTransitive = false }
minecraft("com.mojang:minecraft:$minecraftVersion")
forge("net.minecraftforge:forge:$minecraftVersion-$forgeVersion")
mappings(loom.layered {
officialMojangMappings()
// TODO: change this when updating to 1.19.2
parchment("org.parchmentmc.data:$mappingsChannel-$minecraftVersion.2:$mappingsVersion@zip")
})
modCompileOnly("me.shedaniel:RoughlyEnoughItems-forge:$reiVersion") // required for forge rei plugin | api does not work here!
modCompileOnly("mezz.jei:jei-$minecraftVersion-forge:$jeiVersion") // required for common jei plugin and mixin
// runtime only
when (forgeRecipeViewer) {
"rei" -> modLocalRuntime("me.shedaniel:RoughlyEnoughItems-forge:$reiVersion")
"jei" -> modLocalRuntime("mezz.jei:jei-$minecraftVersion-forge:$jeiVersion")
else -> throw GradleException("Invalid forgeRecipeViewer value: $forgeRecipeViewer")
}
// required for common kubejs plugin and forge runtime
modCompileOnly(modLocalRuntime("dev.latvian.mods:kubejs-forge:$kubejsVersion")!!)
fileTree("$extraModsDirectory-$minecraftVersion") { include("**/*.jar") }
.forEach { f ->
val sepIndex = f.nameWithoutExtension.lastIndexOf('-')
if (sepIndex == -1) {
throw IllegalArgumentException("Invalid mod name: ${f.nameWithoutExtension}")
}
val mod = f.nameWithoutExtension.substring(0, sepIndex)
val version = f.nameWithoutExtension.substring(sepIndex + 1)
println("Extra mod $mod with version $version detected")
modLocalRuntime("$extraModsDirectory:$mod:$version")
}
// JUnit Tests
testImplementation(project(":Common"))
testImplementation(commonTests)
testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
} }
tasks { tasks {
// TODO: test if this is necessary
jar { jar {
finalizedBy("reobfJar") from("LICENSE") {
rename { "${it}_${modName}" }
}
}
// TODO: test if this is necessary
processResources {
from(project(":Common").sourceSets.main.get().resources)
inputs.property("version", project.version)
filesMatching("META-INF/mods.toml") {
expand("version" to project.version)
}
} }
withType<JavaCompile> { withType<JavaCompile> {
source(project(":Common").sourceSets.main.get().allSource) source(project(":Common").sourceSets.main.get().allSource)
} }
withType<Test> {
useJUnitPlatform()
}
processResources {
from(project(":Common").sourceSets.main.get().resources)
}
} }
publishing { publishing {
publications { publications {
register("mavenJava", MavenPublication::class) { register("mavenJava", MavenPublication::class) {
artifactId = baseArchiveName artifactId = baseArchiveName
artifact(tasks.jar) from(components["java"])
} }
} }

1
Forge/gradle.properties Normal file
View file

@ -0,0 +1 @@
loom.platform = forge

View file

@ -1,12 +1,24 @@
package com.almostreliable.unified; package com.almostreliable.unified;
import com.almostreliable.unified.recipe.ClientRecipeTracker;
import net.minecraft.core.Registry;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegisterEvent;
@Mod(BuildConfig.MOD_ID) @Mod(BuildConfig.MOD_ID)
public class AlmostUnifiedForge { public class AlmostUnifiedForge {
public AlmostUnifiedForge() { public AlmostUnifiedForge() {
var modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
modEventBus.addListener((RegisterEvent event) -> {
if (event.getRegistryKey().equals(Registry.RECIPE_SERIALIZER_REGISTRY)) {
ForgeRegistries.RECIPE_SERIALIZERS.register(ClientRecipeTracker.ID, ClientRecipeTracker.SERIALIZER);
}
if (event.getRegistryKey().equals(Registry.RECIPE_TYPE_REGISTRY)) {
ForgeRegistries.RECIPE_TYPES.register(ClientRecipeTracker.ID, ClientRecipeTracker.TYPE);
}
});
} }
} }

View file

@ -3,9 +3,12 @@ package com.almostreliable.unified;
import com.almostreliable.unified.api.ModConstants; import com.almostreliable.unified.api.ModConstants;
import com.almostreliable.unified.compat.IERecipeUnifier; import com.almostreliable.unified.compat.IERecipeUnifier;
import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory; import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.FMLPaths; import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import java.nio.file.Path; import java.nio.file.Path;
@ -18,6 +21,9 @@ public class AlmostUnifiedPlatformForge implements AlmostUnifiedPlatform {
@Override @Override
public boolean isModLoaded(String modId) { public boolean isModLoaded(String modId) {
if (ModList.get() == null) {
return LoadingModList.get().getMods().stream().map(ModInfo::getModId).anyMatch(modId::equals);
}
return ModList.get().isLoaded(modId); return ModList.get().isLoaded(modId);
} }
@ -26,6 +32,11 @@ public class AlmostUnifiedPlatformForge implements AlmostUnifiedPlatform {
return !FMLLoader.isProduction(); return !FMLLoader.isProduction();
} }
@Override
public boolean isClient() {
return FMLLoader.getDist() == Dist.CLIENT;
}
@Override @Override
public Path getConfigPath() { public Path getConfigPath() {
return FMLPaths.CONFIGDIR.get().resolve(BuildConfig.MOD_ID); return FMLPaths.CONFIGDIR.get().resolve(BuildConfig.MOD_ID);

View file

@ -0,0 +1,89 @@
package com.almostreliable.unified.compat;
import com.almostreliable.unified.recipe.CRTLookup;
import com.almostreliable.unified.recipe.ClientRecipeTracker;
import com.almostreliable.unified.utils.Utils;
import me.shedaniel.math.Rectangle;
import me.shedaniel.rei.api.client.gui.DisplayRenderer;
import me.shedaniel.rei.api.client.gui.widgets.Widget;
import me.shedaniel.rei.api.client.gui.widgets.Widgets;
import me.shedaniel.rei.api.client.plugins.REIClientPlugin;
import me.shedaniel.rei.api.client.registry.category.ButtonArea;
import me.shedaniel.rei.api.client.registry.category.CategoryRegistry;
import me.shedaniel.rei.api.client.registry.category.extension.CategoryExtensionProvider;
import me.shedaniel.rei.api.client.registry.display.DisplayCategory;
import me.shedaniel.rei.api.client.registry.display.DisplayCategoryView;
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.forge.REIPlugin;
import net.minecraft.client.renderer.Rect2i;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.List;
@REIPlugin(Dist.CLIENT)
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("UnstableApiUsage")
public class AlmostREI implements REIClientPlugin {
@Override
public void postStage(PluginManager<REIClientPlugin> manager, ReloadStage stage) {
if (stage != ReloadStage.END || !manager.equals(PluginManager.getClientInstance())) return;
CategoryRegistry.getInstance().forEach(category -> {
IndicatorExtension extension = new IndicatorExtension(category.getPlusButtonArea().orElse(null));
category.registerExtension(Utils.cast(extension));
});
}
@SuppressWarnings("OverrideOnly")
private record IndicatorExtension(@Nullable ButtonArea plusButtonArea)
implements CategoryExtensionProvider<Display> {
@Override
public DisplayCategoryView<Display> provide(Display display, DisplayCategory<Display> category, DisplayCategoryView<Display> lastView) {
return display
.getDisplayLocation()
.map(CRTLookup::getLink)
.map(link -> (DisplayCategoryView<Display>) new IndicatorView(lastView, link))
.orElse(lastView);
}
private final class IndicatorView implements DisplayCategoryView<Display> {
private final DisplayCategoryView<Display> lastView;
private final ClientRecipeTracker.ClientRecipeLink link;
private IndicatorView(DisplayCategoryView<Display> lastView, ClientRecipeTracker.ClientRecipeLink link) {
this.lastView = lastView;
this.link = link;
}
@Override
public DisplayRenderer getDisplayRenderer(Display display) {
return lastView.getDisplayRenderer(display);
}
@Override
public List<Widget> setupDisplay(Display display, Rectangle bounds) {
var widgets = lastView.setupDisplay(display, bounds);
var area = calculateArea(bounds);
widgets.add(Widgets.createDrawableWidget((helper, stack, mX, mY, delta) ->
RecipeIndicator.renderIndicator(stack, area)));
var tooltipArea = new Rectangle(area.getX(), area.getY(), area.getWidth(), area.getHeight());
widgets.add(Widgets.createTooltip(tooltipArea, RecipeIndicator.constructTooltip(link)));
return widgets;
}
private Rect2i calculateArea(Rectangle bounds) {
if (plusButtonArea != null) {
var area = plusButtonArea.get(bounds);
return new Rect2i(area.x, area.y - area.height - 2, area.width, area.height);
}
return new Rect2i(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
}
}

View file

@ -2,51 +2,39 @@ import java.text.SimpleDateFormat
import java.util.* import java.util.*
val license: String by project val license: String by project
val extraModsDirectory: String by project
val minecraftVersion: String by project val minecraftVersion: String by project
val forgeMinVersion: String by project
val modId: String by project val modId: String by project
val modName: String by project val modName: String by project
val modAuthor: String by project val modAuthor: String by project
val modDescription: String by project val modDescription: String by project
val forgeMinVersion: String by project
val extraModsDirectory: String by project
val githubUser: String by project val githubUser: String by project
val githubRepo: String by project val githubRepo: String by project
plugins { plugins {
java java
idea
} }
allprojects { subprojects {
apply(plugin = "maven-publish")
apply(plugin = "java")
apply(plugin = "eclipse")
apply(plugin = "idea")
repositories { repositories {
maven("https://maven.parchmentmc.org/")
maven("https://maven.shedaniel.me")
maven("https://dvs1.progwml6.com/files/maven/")
maven("https://maven.saps.dev/minecraft")
flatDir { flatDir {
name = extraModsDirectory name = extraModsDirectory
dir(file("$extraModsDirectory-$minecraftVersion")) dir(file("$extraModsDirectory-$minecraftVersion"))
} }
mavenLocal()
mavenCentral()
maven("https://repo.spongepowered.org/repository/maven-public/")
maven("https://maven.shedaniel.me")
maven("https://dvs1.progwml6.com/files/maven/")
maven("https://maven.saps.dev/minecraft") {
content {
includeGroup("dev.latvian.mods")
} }
}
}
tasks.withType<GenerateModuleMetadata> {
enabled = false
}
}
subprojects {
apply(plugin = "java")
extensions.configure<JavaPluginExtension> { extensions.configure<JavaPluginExtension> {
toolchain.languageVersion.set(JavaLanguageVersion.of(17)) toolchain.languageVersion.set(JavaLanguageVersion.of(17))
withJavadocJar()
withSourcesJar() withSourcesJar()
} }
@ -67,22 +55,18 @@ subprojects {
) )
} }
} }
withType<JavaCompile> {
options.encoding = "UTF-8"
options.release.set(17)
}
processResources { processResources {
val resourceTargets = listOf("META-INF/mods.toml", "pack.mcmeta", "fabric.mod.json") val resourceTargets = listOf("META-INF/mods.toml", "pack.mcmeta", "fabric.mod.json")
val replaceProperties = mapOf( val replaceProperties = mapOf(
"version" to project.version as String, "version" to project.version as String,
"license" to license, "license" to license,
"minecraftVersion" to minecraftVersion,
"forgeMinVersion" to forgeMinVersion,
"modId" to modId, "modId" to modId,
"modName" to modName, "modName" to modName,
"minecraftVersion" to minecraftVersion,
"modAuthor" to modAuthor, "modAuthor" to modAuthor,
"modDescription" to modDescription, "modDescription" to modDescription,
"forgeMinVersion" to forgeMinVersion,
"githubUser" to githubUser, "githubUser" to githubUser,
"githubRepo" to githubRepo "githubRepo" to githubRepo
) )
@ -92,5 +76,12 @@ subprojects {
expand(replaceProperties) expand(replaceProperties)
} }
} }
withType<JavaCompile> {
options.encoding = "UTF-8"
options.release.set(17)
}
withType<GenerateModuleMetadata> {
enabled = false
}
} }
} }

View file

@ -2,7 +2,12 @@
version = 0.0.8 version = 0.0.8
group = com.almostreliable.unified group = com.almostreliable.unified
license = LGPL-3.0 license = LGPL-3.0
mixinVersion = 0.8.5 junitVersion = 5.9.0
# Runtime Settings
extraModsDirectory = extra-mods
forgeRecipeViewer = rei
fabricRecipeViewer = jei
# Common # Common
minecraftVersion = 1.19 minecraftVersion = 1.19
@ -16,16 +21,15 @@ fabricVersion = 0.58.0+1.19
fabricLoaderVersion = 0.14.9 fabricLoaderVersion = 0.14.9
# Dependencies # Dependencies
extraModsDirectory = extra-mods
reiVersion = 9.1.537 reiVersion = 9.1.537
jeiVersion = 11+ jeiVersion = 11.1.1.239
kubejsVersion = 1900.5.5-build.27 kubejsVersion = 1900.5.5-build.27
# Mappings # Mappings
mappingsChannel = parchment mappingsChannel = parchment
mappingsVersion = 2022.09.04 mappingsVersion = 2022.09.04
# Mod options # Mod Info
modId = almostunified modId = almostunified
modName = AlmostUnified modName = AlmostUnified
modAuthor = AlmostReliable modAuthor = AlmostReliable

Binary file not shown.

View file

@ -1,26 +1,15 @@
pluginManagement { pluginManagement {
repositories { repositories {
maven("https://maven.fabricmc.net/")
maven("https://maven.minecraftforge.net/")
maven("https://maven.architectury.dev/")
maven("https://repo.spongepowered.org/repository/maven-public/")
gradlePluginPortal() gradlePluginPortal()
maven("https://maven.fabricmc.net/") { mavenCentral()
name = "Fabric" mavenLocal()
}
maven("https://repo.spongepowered.org/repository/maven-public/") {
name = "Sponge Snapshots"
}
maven("https://maven.minecraftforge.net") {
name = "Forge"
}
maven("https://maven.parchmentmc.org") {
name = "ParchmentMC"
}
} }
resolutionStrategy { resolutionStrategy {
eachPlugin { eachPlugin {
// If we request Forge, actually give it the correct artifact.
if (requested.id.id == "net.minecraftforge.gradle") {
useModule("${requested.id}:ForgeGradle:${requested.version}")
}
if (requested.id.id == "org.spongepowered.mixin") { if (requested.id.id == "org.spongepowered.mixin") {
useModule("org.spongepowered:mixingradle:${requested.version}") useModule("org.spongepowered:mixingradle:${requested.version}")
} }