diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..d3da24f Binary files /dev/null and b/.DS_Store differ diff --git a/build.gradle b/build.gradle index 3b32023..4dfce04 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,8 @@ plugins { project.version = "0.0.1-SNAPSHOT" +import net.fabricmc.loom.task.RunClientTask + allprojects { project.group = "textile-api" @@ -31,12 +33,30 @@ allprojects { } } + sourceSets { + testmod { + compileClasspath += main.compileClasspath + runtimeClasspath += main.runtimeClasspath + } + } + + task runTestmodClient(type: RunClientTask) { + classpath sourceSets.testmod.runtimeClasspath + } + dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.textilemc:yarrn:inf-20100618+build20d332:v2" - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" +// modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // IMPORTANT + // Until a certain pr is merged, make sure you + // add -Dfabric.gameVersion=20100618 to your run + // config's vm arguments otherwise bad + + modImplementation "com.github.sfPlayer1:fabric-loader:adad4b3" // Some dependencies aren't gotten by the loader, so we add them ourselves implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.8.1' @@ -104,6 +124,16 @@ allprojects { } } +subprojects { + dependencies { + testmodCompile sourceSets.main.output + } +} + +sourceSets { + testmod +} + javadoc { options { source = "8" @@ -114,7 +144,7 @@ javadoc { 'https://guava.dev/releases/21.0/api/docs/', 'https://asm.ow2.io/javadoc/', 'https://docs.oracle.com/javase/8/docs/api/', - 'http://jenkins.liteloader.com/job/Mixin/javadoc/', + 'https://jenkins.liteloader.com/job/Mixin/javadoc/', 'https://logging.apache.org/log4j/2.x/log4j-api/apidocs/' // Need to add minecraft jd publication etc once there is one available ) diff --git a/textile-api-base/src/main/java/net/textilemc/textile/api/util/GameInstanceUtils.java b/textile-api-base/src/main/java/net/textilemc/textile/api/util/GameInstanceUtils.java new file mode 100644 index 0000000..176c496 --- /dev/null +++ b/textile-api-base/src/main/java/net/textilemc/textile/api/util/GameInstanceUtils.java @@ -0,0 +1,27 @@ +package net.textilemc.textile.api.util; + +import java.util.Objects; + +import org.jetbrains.annotations.NotNull; + +import net.minecraft.client.MinecraftApplet; +import net.minecraft.client.MinecraftClient; + +/** + * Utilities to get game instances. + */ +@SuppressWarnings("FieldMayBeFinal") +public class GameInstanceUtils { + private static MinecraftClient CLIENT = null; + private static MinecraftApplet APPLET = null; + + @NotNull + public static MinecraftClient getMinecraftClient() { + return Objects.requireNonNull(CLIENT, "Requested Minecraft client too early!"); + } + + @NotNull + public static MinecraftApplet getMinecraftApplet() { + return Objects.requireNonNull(APPLET, "Requested Minecraft applet too early!"); + } +} diff --git a/textile-api-base/src/main/java/net/textilemc/textile/mixin/base/MinecraftAppletMixin.java b/textile-api-base/src/main/java/net/textilemc/textile/mixin/base/MinecraftAppletMixin.java new file mode 100644 index 0000000..e996e40 --- /dev/null +++ b/textile-api-base/src/main/java/net/textilemc/textile/mixin/base/MinecraftAppletMixin.java @@ -0,0 +1,44 @@ +package net.textilemc.textile.mixin.base; + +import java.lang.reflect.Field; + +import net.textilemc.textile.api.util.GameInstanceUtils; +import org.objectweb.asm.Opcodes; +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 net.minecraft.client.MinecraftApplet; +import net.minecraft.client.MinecraftClient; + +@Mixin(MinecraftApplet.class) +public class MinecraftAppletMixin { + @Shadow + private MinecraftClient client; + + @Inject(method = "init", at = @At("HEAD")) + public void preInit(CallbackInfo ci) { + try { + //noinspection JavaReflectionMemberAccess + Field f = GameInstanceUtils.class.getField("APPLET"); + f.setAccessible(true); + f.set(null, this); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new AssertionError(); + } + } + + @Inject(method = "init", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/MinecraftClient;field_969:Z")) + public void afterInit(CallbackInfo ci) { + try { + //noinspection JavaReflectionMemberAccess + Field f = GameInstanceUtils.class.getField("CLIENT"); + f.setAccessible(true); + f.set(null, this.client); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new AssertionError(); + } + } +} diff --git a/textile-api-base/src/main/resources/fabric.mod.json b/textile-api-base/src/main/resources/fabric.mod.json index d7182aa..5cb3855 100644 --- a/textile-api-base/src/main/resources/fabric.mod.json +++ b/textile-api-base/src/main/resources/fabric.mod.json @@ -16,5 +16,8 @@ "depends": { "fabricloader": ">=0.4.0" }, - "description": "Contains the essentials for Textile API modules." + "description": "Contains the essentials for Textile API modules.", + "mixins": [ + "textile-api-base.mixins.json" + ] } diff --git a/textile-api-base/src/main/resources/textile-api-base.mixins.json b/textile-api-base/src/main/resources/textile-api-base.mixins.json new file mode 100644 index 0000000..dc41c30 --- /dev/null +++ b/textile-api-base/src/main/resources/textile-api-base.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "package": "net.textilemc.textile.mixin.base", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "MinecraftAppletMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/CraftingRecipeType.java b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/CraftingRecipeType.java new file mode 100644 index 0000000..4fef469 --- /dev/null +++ b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/CraftingRecipeType.java @@ -0,0 +1,46 @@ +package net.textilemc.textile.api.recipe.v1; + +import java.util.Arrays; +import java.util.List; + +import net.textilemc.textile.mixin.recipe.CraftingInventoryAccessor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.item.ItemStack; +import net.minecraft.storage.CraftingInventory; +import net.minecraft.world.World; + +/** + * Base class for custom crafting recipe types. + */ +public interface CraftingRecipeType { + /** + * @param inventory The inventory. May be null + * @return Output stack + */ + ItemStack getOutput(List inventory); + + /** + * @param inventory The inventory + * @return Whether the inventory's stacks match + */ + default boolean matches(CraftingInventory inventory, World world) { + //noinspection ConstantConditions + return this.matches(Arrays.asList(((CraftingInventoryAccessor) (Object) inventory).getField_871().clone()), world); + } + + /** + * @param stacks The item stacks + * @return Whether the inventory's stacks match + */ + boolean matches(List stacks, World world); + + /** + * Called after this recipe has been matched and consumed. + * + *

This can be used to add recipe remainders, damage items, etc.

+ */ + default void afterEvaluate(@NotNull CraftingInventory inventory, World world) { + } +} diff --git a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/Dispatcher.java b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/Dispatcher.java new file mode 100644 index 0000000..413fc38 --- /dev/null +++ b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/Dispatcher.java @@ -0,0 +1,26 @@ +package net.textilemc.textile.api.recipe.v1; + +import java.util.Collections; +import java.util.List; + +import net.textilemc.textile.impl.recipe.DispatcherHolder; + +import net.minecraft.item.ItemStack; + +/** + * Stores a bunch of recipes + */ +public interface Dispatcher { + void accept(T recipe); + + ItemStack getOutput(List stacks); + + static void register(Dispatcher dispatcher) { + DispatcherHolder.DISPATCHERS.add(dispatcher); + } + + static List> getAll() { + return Collections.unmodifiableList(DispatcherHolder.DISPATCHERS); + } + +} diff --git a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/ShapelessRecipe.java b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/ShapelessRecipe.java new file mode 100644 index 0000000..b97b459 --- /dev/null +++ b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/ShapelessRecipe.java @@ -0,0 +1,83 @@ +package net.textilemc.textile.api.recipe.v1; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Preconditions; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +/** + * Recipe type for recipes that do not have any specific shape. + */ +public class ShapelessRecipe implements CraftingRecipeType { + public static final Dispatcher DISPATCHER = new SimpleDispatcher<>(); + private final ItemStack output; + private final ArrayList ingredients; + + /** + * @param output The output stack + * @param ingredients The ingredients + * @throws IllegalArgumentException If the size of ingredients is 0 + */ + public ShapelessRecipe(ItemStack output, ArrayList ingredients) { + Preconditions.checkArgument(ingredients.size() > 0); + this.output = output; + this.ingredients = ingredients; + } + + @Override + public ItemStack getOutput(List stacks) { + return this.output; + } + @Override + public boolean matches(List items, World world) { + //noinspection unchecked + ArrayList stacks = (ArrayList) this.ingredients.clone(); + + for (ItemStack stack : items) { + for (ItemStack itemStack : stacks) { + if (stack != null && stack.id == itemStack.id && (itemStack.meta == 32767 || stack.meta == itemStack.meta)) { + stacks.remove(itemStack); + break; + } + } + } + + return stacks.isEmpty(); + } + + public static Builder builder(ItemStack output) { + return new ShapelessRecipeBuilder(output); + } + + static { + Dispatcher.register(DISPATCHER); + } + + /** + * Helper class for creating shapeless recipes + */ + public interface Builder { + /** + * @param stack The item stack to be added to the shapeless recipe + * @return This builder + */ + Builder add(ItemStack stack); + + /** + * @param item The item to be added to the shapeless recipe + * @return This builder + */ + Builder add(Item item); + + /** + * Converts this builder to a recipe + * + * @return a shapeless recipe + */ + ShapelessRecipe build(); + } +} diff --git a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/ShapelessRecipeBuilder.java b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/ShapelessRecipeBuilder.java new file mode 100644 index 0000000..8afb54e --- /dev/null +++ b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/ShapelessRecipeBuilder.java @@ -0,0 +1,33 @@ +package net.textilemc.textile.api.recipe.v1; + +import java.util.ArrayList; +import java.util.Objects; + +import com.google.common.base.Preconditions; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +public class ShapelessRecipeBuilder implements ShapelessRecipe.Builder { + private final ItemStack output; + private final ArrayList stacks = new ArrayList<>(); + + ShapelessRecipeBuilder(ItemStack output) { + this.output = output; + } + + public ShapelessRecipeBuilder add(ItemStack stack) { + this.stacks.add(Objects.requireNonNull(stack)); + return this; + } + + public ShapelessRecipeBuilder add(Item item) { + return this.add(new ItemStack(Objects.requireNonNull(item))); + } + + @SuppressWarnings("unchecked") + public ShapelessRecipe build() { + Preconditions.checkArgument(this.stacks.size() > 0, "Empty Shapeless Recipe"); + return new ShapelessRecipe(this.output, (ArrayList) this.stacks.clone()); + } +} diff --git a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/SimpleDispatcher.java b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/SimpleDispatcher.java new file mode 100644 index 0000000..0936516 --- /dev/null +++ b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/SimpleDispatcher.java @@ -0,0 +1,34 @@ +package net.textilemc.textile.api.recipe.v1; + +import java.util.ArrayList; +import java.util.List; + +import net.textilemc.textile.api.util.GameInstanceUtils; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.item.ItemStack; + +/** + * A simple dispatcher implementation. + * @param The recipe type + */ +public class SimpleDispatcher implements Dispatcher { + private final List recipes = new ArrayList<>(); + + @Override + public void accept(T recipe) { + this.recipes.add(recipe); + } + + @Nullable + @Override + public ItemStack getOutput(List stacks) { + for (T recipe : this.recipes) { + if (recipe.matches(stacks, GameInstanceUtils.getMinecraftClient().world)) { + return recipe.getOutput(stacks); + } + } + + return null; + } +} diff --git a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/event/v1/ShapedRecipeCallback.java b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/event/ShapedRecipeCallback.java similarity index 92% rename from textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/event/v1/ShapedRecipeCallback.java rename to textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/event/ShapedRecipeCallback.java index 36ef972..91f0323 100644 --- a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/event/v1/ShapedRecipeCallback.java +++ b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/api/recipe/v1/event/ShapedRecipeCallback.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package net.textilemc.textile.api.recipe.event.v1; +package net.textilemc.textile.api.recipe.v1.event; import net.textilemc.textile.api.event.Event; import net.textilemc.textile.api.event.EventFactory; @@ -41,6 +41,6 @@ public interface ShapedRecipeCallback { @FunctionalInterface interface ShapedRecipeConsumer { - void addShapedRecipe(ItemStack var1, Object... args); + void addShapedRecipe(ItemStack output, Object... args); } } diff --git a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/impl/recipe/DispatcherHolder.java b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/impl/recipe/DispatcherHolder.java new file mode 100644 index 0000000..2b54f93 --- /dev/null +++ b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/impl/recipe/DispatcherHolder.java @@ -0,0 +1,11 @@ +package net.textilemc.textile.impl.recipe; + +import java.util.LinkedList; + +import net.textilemc.textile.api.recipe.v1.Dispatcher; +import net.textilemc.textile.api.recipe.v1.ShapelessRecipe; +import net.textilemc.textile.api.recipe.v1.SimpleDispatcher; + +public class DispatcherHolder { + public static final LinkedList> DISPATCHERS = new LinkedList<>(); +} diff --git a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/impl/recipe/RecipeTest.java b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/impl/recipe/RecipeTest.java new file mode 100644 index 0000000..43f9c2d --- /dev/null +++ b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/impl/recipe/RecipeTest.java @@ -0,0 +1,23 @@ +package net.textilemc.textile.impl.recipe; + +import net.textilemc.textile.api.recipe.v1.ShapelessRecipe; + +import net.minecraft.block.Block; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint; + +public class RecipeTest implements PreLaunchEntrypoint { + @Override + public void onPreLaunch() { + System.out.println("Shapeless Recipes"); + ShapelessRecipe.DISPATCHER.accept(ShapelessRecipe.builder(new ItemStack(Item.COAL)) + .add(new ItemStack(Block.LOG)) + .add(new ItemStack(Block.DIRT)) + .add(new ItemStack(Block.DIRT)) + .add(new ItemStack(Block.DIRT)) + .add(new ItemStack(Block.DIRT)) + .build()); + } +} diff --git a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/mixin/recipe/CraftingInventoryAccessor.java b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/mixin/recipe/CraftingInventoryAccessor.java new file mode 100644 index 0000000..0f7db46 --- /dev/null +++ b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/mixin/recipe/CraftingInventoryAccessor.java @@ -0,0 +1,13 @@ +package net.textilemc.textile.mixin.recipe; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.item.ItemStack; +import net.minecraft.storage.CraftingInventory; + +@Mixin(CraftingInventory.class) +public interface CraftingInventoryAccessor { + @Accessor + ItemStack[] getField_871(); +} diff --git a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/mixin/recipe/MinecraftAppletAccessor.java b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/mixin/recipe/MinecraftAppletAccessor.java new file mode 100644 index 0000000..08c2af8 --- /dev/null +++ b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/mixin/recipe/MinecraftAppletAccessor.java @@ -0,0 +1,13 @@ +package net.textilemc.textile.mixin.recipe; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.MinecraftApplet; +import net.minecraft.client.MinecraftClient; + +@Mixin(MinecraftApplet.class) +public interface MinecraftAppletAccessor { + @Accessor + MinecraftClient getClient(); +} diff --git a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/mixin/recipe/RecipeDispatcherMixin.java b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/mixin/recipe/RecipeDispatcherMixin.java index 762966f..17089ec 100644 --- a/textile-recipe-api-v1/src/main/java/net/textilemc/textile/mixin/recipe/RecipeDispatcherMixin.java +++ b/textile-recipe-api-v1/src/main/java/net/textilemc/textile/mixin/recipe/RecipeDispatcherMixin.java @@ -17,15 +17,21 @@ package net.textilemc.textile.mixin.recipe; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; -import net.textilemc.textile.api.recipe.event.v1.ShapedRecipeCallback; +import net.textilemc.textile.api.recipe.v1.Dispatcher; +import net.textilemc.textile.api.recipe.v1.event.ShapedRecipeCallback; +import net.textilemc.textile.impl.recipe.DispatcherHolder; 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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import net.minecraft.item.ItemStack; import net.minecraft.recipe.RecipeDispatcher; @@ -41,4 +47,15 @@ public void interceptSort(List list, Comparator c) { //noinspection Java8ListSort Collections.sort(list, c); } + + @Inject(method = "method_304", at = @At("RETURN"), cancellable = true) + public void beforeReturn(int[] is, CallbackInfoReturnable cir) { + for (Dispatcher dispatcher : DispatcherHolder.DISPATCHERS) { + ItemStack out = dispatcher.getOutput(Arrays.stream(is).mapToObj(ItemStack::new).collect(Collectors.toList())); + if (out != null) { + cir.setReturnValue(out); + break; + } + } + } } diff --git a/textile-recipe-api-v1/src/main/resources/fabric.mod.json b/textile-recipe-api-v1/src/main/resources/fabric.mod.json index cf3558b..8fb2822 100644 --- a/textile-recipe-api-v1/src/main/resources/fabric.mod.json +++ b/textile-recipe-api-v1/src/main/resources/fabric.mod.json @@ -20,5 +20,10 @@ "depends": { "fabricloader": "*", "minecraft": "20100618" + }, + "entrypoints": { + "preLaunch": [ + "net.textilemc.textile.impl.recipe.RecipeTest" + ] } } diff --git a/textile-recipe-api-v1/src/main/resources/textile-recipe-api-v1.mixins.json b/textile-recipe-api-v1/src/main/resources/textile-recipe-api-v1.mixins.json index 6f16d35..d98d618 100644 --- a/textile-recipe-api-v1/src/main/resources/textile-recipe-api-v1.mixins.json +++ b/textile-recipe-api-v1/src/main/resources/textile-recipe-api-v1.mixins.json @@ -3,6 +3,9 @@ "package": "net.textilemc.textile.mixin.recipe", "compatibilityLevel": "JAVA_8", "mixins": [ + "CraftingInventoryAccessor", + "CraftingInventoryMixin", + "MinecraftAppletAccessor", "RecipeDispatcherMixin" ], "injectors": { diff --git a/textile-recipe-api-v1/src/testmod/resources/fabric.mod.json b/textile-recipe-api-v1/src/testmod/resources/fabric.mod.json new file mode 100644 index 0000000..df4ee51 --- /dev/null +++ b/textile-recipe-api-v1/src/testmod/resources/fabric.mod.json @@ -0,0 +1,13 @@ +{ + "schemaVersion": 1, + "id": "textile-recipe-api-v1-test", + "version": "0.1.0", + "name": "Textile Recipe API (v1) Testmod", + "authors": [ + "TextileMC" + ], + "contact": { + "issues": "https://github.com/TextileMC/textile-api/issues", + "sources": "https://github.com/TextileMC/textile-api" + } +}