diff --git a/src/main/java/net/wovenmc/woven/api/item/settings/TemplateAPI.java b/src/main/java/net/wovenmc/woven/api/item/settings/EquipmentHandler.java
similarity index 64%
rename from src/main/java/net/wovenmc/woven/api/item/settings/TemplateAPI.java
rename to src/main/java/net/wovenmc/woven/api/item/settings/EquipmentHandler.java
index 9606458..3629489 100644
--- a/src/main/java/net/wovenmc/woven/api/item/settings/TemplateAPI.java
+++ b/src/main/java/net/wovenmc/woven/api/item/settings/EquipmentHandler.java
@@ -16,10 +16,17 @@
package net.wovenmc.woven.api.item.settings;
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.item.ItemStack;
+
/**
- * Represents a dummy class as template for API.
- *
- * This should not be present in any module.
+ * An interface for handling the equipment slots of non-armor items.
*/
-public class TemplateAPI {
+@FunctionalInterface
+public interface EquipmentHandler {
+ /**
+ * @param stack The stack to equip.
+ * @return The slot the stack should be equipped to.
+ */
+ EquipmentSlot getEquipmentSlot(ItemStack stack);
}
diff --git a/src/main/java/net/wovenmc/woven/api/item/settings/MeterComponent.java b/src/main/java/net/wovenmc/woven/api/item/settings/MeterComponent.java
new file mode 100644
index 0000000..afe64c7
--- /dev/null
+++ b/src/main/java/net/wovenmc/woven/api/item/settings/MeterComponent.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2020 WovenMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.wovenmc.woven.api.item.settings;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.math.MathHelper;
+
+/**
+ * A component that displays a colored meter on an item in a GUI, similar to the vanilla damage bar.
+ */
+public class MeterComponent {
+ private final LevelHandler levelHandler;
+ private final ColorHandler colorHandler;
+ private final boolean displayAtFull;
+
+ private MeterComponent(LevelHandler levelHandler, ColorHandler colorHandler,
+ boolean displayAtFull) {
+ this.levelHandler = levelHandler;
+ this.colorHandler = colorHandler;
+ this.displayAtFull = displayAtFull;
+ }
+
+ /**
+ * Get the current level of the meter.
+ * @param stack The item stack to get the level for.
+ * @return The current level, as a float between 0 and 1 inclusive.
+ */
+ public float getLevel(ItemStack stack) {
+ return levelHandler.getLevel(stack);
+ }
+
+ /**
+ * Get the current color of the meter.
+ * @param stack The item stack to get the color for.
+ * @return The current color as an RGB value.
+ */
+ public int getColor(ItemStack stack) {
+ return colorHandler.getColor(stack, levelHandler.getLevel(stack));
+ }
+
+ /**
+ * @return true if the meter should be rendered when the value is 1.
+ */
+ public boolean displayAtFull() {
+ return displayAtFull;
+ }
+
+ /**
+ * A builder for meter components.
+ */
+ public static class Builder {
+ private LevelHandler levelHandler = stack ->
+ (stack.getMaxDamage() - stack.getDamage()) / (float) stack.getMaxDamage();
+ private ColorHandler colorHandler = (stack, level) ->
+ MathHelper.hsvToRgb(level / 3F, 1F, 1F);
+ private boolean displayAtFull = false;
+
+ /**
+ * @param handler The handler for getting the current level of a meter.
+ * @return The builder with the handler set.
+ */
+ public Builder levelFunction(LevelHandler handler) {
+ this.levelHandler = handler;
+ return this;
+ }
+
+ /**
+ * @param handler The handler for getting the current color of a meter.
+ * @return The builder with the handler set.
+ */
+ public Builder colorHandler(ColorHandler handler) {
+ this.colorHandler = handler;
+ return this;
+ }
+
+ /**
+ * @return The builder with the flag for displaying at full set.
+ */
+ public Builder displayAtFull() {
+ this.displayAtFull = true;
+ return this;
+ }
+
+ /**
+ * @return A built meter component.
+ */
+ public MeterComponent build() {
+ return new MeterComponent(levelHandler, colorHandler, displayAtFull);
+ }
+ }
+
+ /**
+ * An interface for determining the level of a meter.
+ */
+ @FunctionalInterface
+ public interface LevelHandler {
+ /**
+ * @param stack The stack to get the meter level for.
+ * @return The level of the meter, as a float from 0 to 1 inclusive.
+ */
+ float getLevel(ItemStack stack);
+ }
+
+ /**
+ * An interface for determining the color of a meter.
+ */
+ @FunctionalInterface
+ public interface ColorHandler {
+ /**
+ * @param stack The stack to get the meter color for.
+ * @param value The current level of the meter.
+ * @return The color to use as an RGB value.
+ */
+ int getColor(ItemStack stack, float value);
+ }
+}
diff --git a/src/main/java/net/wovenmc/woven/api/item/settings/RecipeRemainderHandler.java b/src/main/java/net/wovenmc/woven/api/item/settings/RecipeRemainderHandler.java
new file mode 100644
index 0000000..b92cee6
--- /dev/null
+++ b/src/main/java/net/wovenmc/woven/api/item/settings/RecipeRemainderHandler.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020 WovenMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.wovenmc.woven.api.item.settings;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+
+/**
+ * An interface for dynamically handling recipe remainders.
+ */
+@FunctionalInterface
+public interface RecipeRemainderHandler {
+ /**
+ * @param original The original item stack used in the recipe.
+ * @param recipeId The identifier of the original recipe. Hardcoded to {@code minecraft:brewing} for brewing and {@code minecraft:furnace_fuel} for furnace fuel.
+ * @return The item stack that should remain after crafting.
+ */
+ ItemStack getRemainder(ItemStack original, Identifier recipeId);
+}
diff --git a/src/main/java/net/wovenmc/woven/api/item/settings/WovenItemSettings.java b/src/main/java/net/wovenmc/woven/api/item/settings/WovenItemSettings.java
new file mode 100644
index 0000000..42e4104
--- /dev/null
+++ b/src/main/java/net/wovenmc/woven/api/item/settings/WovenItemSettings.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2020 WovenMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.wovenmc.woven.api.item.settings;
+
+import net.wovenmc.woven.mixin.item.settings.MixinItem;
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.item.FoodComponent;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemGroup;
+import net.minecraft.util.Rarity;
+
+/**
+ * An extension to {@link Item.Settings} providing additional hooks for items.
+ */
+public class WovenItemSettings extends Item.Settings {
+ private MeterComponent meterComponent = null;
+ private RecipeRemainderHandler dynamicRecipeRemainder = null;
+ private EquipmentHandler equipmentHandler = null;
+ private boolean selfRemainder = false;
+
+ /**
+ * @param meterComponent The {@link MeterComponent} for this item.
+ * @return The item settings with the component added.
+ */
+ public WovenItemSettings meter(MeterComponent meterComponent) {
+ this.meterComponent = meterComponent;
+ return this;
+ }
+
+ /**
+ * Incompatible with {@link WovenItemSettings#selfRemainder} and {@link WovenItemSettings#recipeRemainder(Item)}.
+ * @param remainder A handler for determining the remainder of an item stack when crafting dynamically.
+ * @return The item settings with the handler added.
+ */
+ public WovenItemSettings recipeRemainder(RecipeRemainderHandler remainder) {
+ if (selfRemainder) {
+ throw new RuntimeException("Unable to have dynamic recipe remainder AND self recipe remainder.");
+ } else if (((MixinItem.ItemSettingsAccessor) this).getRecipeRemainder() != null) {
+ throw new RuntimeException("Unable to have dynamic recipe remainder AND static recipe remainder.");
+ } else {
+ this.dynamicRecipeRemainder = remainder;
+ return this;
+ }
+ }
+
+ /**
+ * Incompatible with {@link WovenItemSettings#recipeRemainder(RecipeRemainderHandler)} and {@link Item.Settings#recipeRemainder(Item)}.
+ * Flags an item to return itself as a recipe remainder without a dynamic remainder handler.
+ * @return The item settings with the flag set.
+ */
+ public WovenItemSettings selfRemainder() {
+ if (dynamicRecipeRemainder != null) {
+ throw new RuntimeException("Unable to have self recipe remainder AND dynamic recipe remainder.");
+ } else if (((MixinItem.ItemSettingsAccessor) this).getRecipeRemainder() != null) {
+ throw new RuntimeException("Unable to have self recipe remainder AND static recipe remainder.");
+ } else {
+ this.selfRemainder = true;
+ return this;
+ }
+ }
+
+ /**
+ * @param equipmentHandler A handler for determining the equipment slot an item stack should go in.
+ * @return The item settings with the handler added.
+ */
+ public WovenItemSettings equipmentHandler(EquipmentHandler equipmentHandler) {
+ this.equipmentHandler = equipmentHandler;
+ return this;
+ }
+
+ @Override
+ public WovenItemSettings group(ItemGroup group) {
+ super.group(group);
+ return this;
+ }
+
+ @Override
+ public WovenItemSettings rarity(Rarity rarity) {
+ super.rarity(rarity);
+ return this;
+ }
+
+ /**
+ * Incompatible with {@link WovenItemSettings#recipeRemainder(RecipeRemainderHandler)} and {@link WovenItemSettings#selfRemainder}.
+ */
+ @Override
+ public WovenItemSettings recipeRemainder(Item recipeRemainder) {
+ if (dynamicRecipeRemainder != null) {
+ throw new RuntimeException("Unable to have static recipe remainder AND dynamic recipe remainder.");
+ } else if (selfRemainder) {
+ throw new RuntimeException("Unable to have static recipe remainder AND self recipe remainder.");
+ } else {
+ super.recipeRemainder(recipeRemainder);
+ return this;
+ }
+ }
+
+ @Override
+ public WovenItemSettings maxDamage(int maxDamage) {
+ super.maxDamage(maxDamage);
+ return this;
+ }
+
+ @Override
+ public WovenItemSettings maxDamageIfAbsent(int maxDamage) {
+ super.maxDamageIfAbsent(maxDamage);
+ return this;
+ }
+
+ @Override
+ public WovenItemSettings maxCount(int maxCount) {
+ super.maxCount(maxCount);
+ return this;
+ }
+
+ @Override
+ public WovenItemSettings food(FoodComponent foodComponent) {
+ super.food(foodComponent);
+ return this;
+ }
+
+ @Override
+ public WovenItemSettings fireproof() {
+ super.fireproof();
+ return this;
+ }
+
+ /**
+ * For internal use.
+ * @return The set meter component, or null if none was set.
+ */
+ @Nullable
+ public MeterComponent getMeterComponent() {
+ return meterComponent;
+ }
+
+ /**
+ * For internal use.
+ * @return The set dynamic recipe remainder, or null if none was set.
+ */
+ @Nullable
+ public RecipeRemainderHandler getRecipeRemainder() {
+ return dynamicRecipeRemainder;
+ }
+
+ /**
+ * For internal use.
+ * @return Whether the self-remainder flag was set.
+ */
+ public boolean remainsSelf() {
+ return selfRemainder;
+ }
+
+ /**
+ * For internal use.
+ * @return The set equipment handler, or null if none was set
+ */
+ @Nullable
+ public EquipmentHandler getEquipmentHandler() {
+ return equipmentHandler;
+ }
+}
diff --git a/src/main/java/net/wovenmc/woven/impl/item/settings/TemplateModInitializer.java b/src/main/java/net/wovenmc/woven/impl/item/settings/WovenItemSettingsHolder.java
similarity index 58%
rename from src/main/java/net/wovenmc/woven/impl/item/settings/TemplateModInitializer.java
rename to src/main/java/net/wovenmc/woven/impl/item/settings/WovenItemSettingsHolder.java
index 98c0c32..ee233f3 100644
--- a/src/main/java/net/wovenmc/woven/impl/item/settings/TemplateModInitializer.java
+++ b/src/main/java/net/wovenmc/woven/impl/item/settings/WovenItemSettingsHolder.java
@@ -16,13 +16,18 @@
package net.wovenmc.woven.impl.item.settings;
-import org.apache.logging.log4j.LogManager;
+import net.wovenmc.woven.api.item.settings.EquipmentHandler;
+import net.wovenmc.woven.api.item.settings.MeterComponent;
+import net.wovenmc.woven.api.item.settings.RecipeRemainderHandler;
+import org.jetbrains.annotations.Nullable;
-import net.fabricmc.api.ModInitializer;
+public interface WovenItemSettingsHolder {
+ @Nullable
+ MeterComponent woven$getMeterComponent();
-public class TemplateModInitializer implements ModInitializer {
- @Override
- public void onInitialize() {
- LogManager.getLogger("woven_module_template").info("Woven Module Template initialized.");
- }
+ @Nullable
+ RecipeRemainderHandler woven$getDynamicRecipeRemainder();
+
+ @Nullable
+ EquipmentHandler woven$getEquipmentHandler();
}
diff --git a/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinAbstractFurnaceBlockEntity.java b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinAbstractFurnaceBlockEntity.java
new file mode 100644
index 0000000..7249eeb
--- /dev/null
+++ b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinAbstractFurnaceBlockEntity.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2020 WovenMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.wovenmc.woven.mixin.item.settings;
+
+import net.wovenmc.woven.impl.item.settings.WovenItemSettingsHolder;
+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.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import net.minecraft.block.entity.AbstractFurnaceBlockEntity;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemConvertible;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.recipe.Recipe;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.collection.DefaultedList;
+
+@Mixin(AbstractFurnaceBlockEntity.class)
+public abstract class MixinAbstractFurnaceBlockEntity {
+ @Shadow
+ protected DefaultedList inventory;
+ private static final Identifier FUEL_ID = new Identifier("furnace_fuel");
+ private final ThreadLocal stack = new ThreadLocal<>();
+
+ @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getRecipeRemainder()Lnet/minecraft/item/Item;"),
+ locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+ private void grabLocalStack(CallbackInfo info, boolean isBurning, boolean isCooking, ItemStack cookingStack, Recipe> recipe) {
+ stack.set(cookingStack);
+ }
+
+ @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getRecipeRemainder()Lnet/minecraft/item/Item;"))
+ private Item hackCustomFuelRemainder(Item origItem) {
+ WovenItemSettingsHolder holder = (WovenItemSettingsHolder) origItem;
+
+ if (holder.woven$getDynamicRecipeRemainder() != null) {
+ //hack to make the furnace realize it should do dynamic recipe remainder due to mixin not being able to change flow
+ //just needs to not be null
+ return Items.AIR;
+ }
+
+ return origItem.getRecipeRemainder();
+ }
+
+ @Redirect(method = "tick", at = @At(value = "NEW", target = "net/minecraft/item/ItemStack"))
+ private ItemStack getNewFuelRemainder(ItemConvertible origItem) {
+ WovenItemSettingsHolder holder = (WovenItemSettingsHolder) origItem;
+
+ if (holder.woven$getDynamicRecipeRemainder() != null) {
+ return holder.woven$getDynamicRecipeRemainder().getRemainder(stack.get(), FUEL_ID);
+ }
+
+ return new ItemStack(origItem);
+ }
+
+ @Inject(method = "craftRecipe", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;decrement(I)V"),
+ cancellable = true, locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+ private void injectSmeltRemainder(Recipe> recipe, CallbackInfo info, ItemStack inStack) {
+ WovenItemSettingsHolder woven = (WovenItemSettingsHolder) inStack.getItem();
+
+ if (woven.woven$getDynamicRecipeRemainder() != null) {
+ ItemStack newStack = woven.woven$getDynamicRecipeRemainder().getRemainder(inStack, recipe.getId());
+
+ if (!newStack.isEmpty()) {
+ this.inventory.set(0, newStack);
+ info.cancel();
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinBannerDuplicateRecipe.java b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinBannerDuplicateRecipe.java
new file mode 100644
index 0000000..57cd025
--- /dev/null
+++ b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinBannerDuplicateRecipe.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020 WovenMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.wovenmc.woven.mixin.item.settings;
+
+import net.wovenmc.woven.impl.item.settings.WovenItemSettingsHolder;
+import org.spongepowered.asm.mixin.Mixin;
+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 org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import net.minecraft.inventory.CraftingInventory;
+import net.minecraft.item.ItemConvertible;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.BannerDuplicateRecipe;
+import net.minecraft.recipe.SpecialCraftingRecipe;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.collection.DefaultedList;
+
+@Mixin(BannerDuplicateRecipe.class)
+public abstract class MixinBannerDuplicateRecipe extends SpecialCraftingRecipe {
+ private final ThreadLocal slot = new ThreadLocal<>();
+
+ public MixinBannerDuplicateRecipe(Identifier id) {
+ super(id);
+ }
+
+ @Inject(method = "getRemainingStacks", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getRecipeRemainder()Lnet/minecraft/item/Item;"),
+ locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+ private void cacheSlot(CraftingInventory inv, CallbackInfoReturnable> info, DefaultedList ret,
+ int index) {
+ slot.set(index);
+ }
+
+ @Redirect(method = "getRemainingStacks", at = @At(value = "NEW", target = "net/minecraft/item/ItemStack"))
+ private ItemStack getNewRemainder(ItemConvertible origItem, CraftingInventory inv) {
+ WovenItemSettingsHolder holder = (WovenItemSettingsHolder) origItem;
+
+ if (holder.woven$getDynamicRecipeRemainder() != null) {
+ return holder.woven$getDynamicRecipeRemainder().getRemainder(inv.getStack(slot.get()), this.getId());
+ }
+
+ return new ItemStack(origItem);
+ }
+}
diff --git a/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinBookCloningRecipe.java b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinBookCloningRecipe.java
new file mode 100644
index 0000000..7e34842
--- /dev/null
+++ b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinBookCloningRecipe.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020 WovenMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.wovenmc.woven.mixin.item.settings;
+
+import net.wovenmc.woven.impl.item.settings.WovenItemSettingsHolder;
+import org.spongepowered.asm.mixin.Mixin;
+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 org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import net.minecraft.inventory.CraftingInventory;
+import net.minecraft.item.ItemConvertible;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.BookCloningRecipe;
+import net.minecraft.recipe.SpecialCraftingRecipe;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.collection.DefaultedList;
+
+@Mixin(BookCloningRecipe.class)
+public abstract class MixinBookCloningRecipe extends SpecialCraftingRecipe {
+ private final ThreadLocal slot = new ThreadLocal<>();
+
+ public MixinBookCloningRecipe(Identifier id) {
+ super(id);
+ }
+
+ @Inject(method = "getRemainingStacks", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getRecipeRemainder()Lnet/minecraft/item/Item;"),
+ locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+ private void cacheSlot(CraftingInventory inv, CallbackInfoReturnable> info, DefaultedList ret,
+ int index) {
+ slot.set(index);
+ }
+
+ @Redirect(method = "getRemainingStacks", at = @At(value = "NEW", target = "net/minecraft/item/ItemStack"))
+ private ItemStack getNewRemainder(ItemConvertible origItem, CraftingInventory inv) {
+ WovenItemSettingsHolder holder = (WovenItemSettingsHolder) origItem;
+
+ if (holder.woven$getDynamicRecipeRemainder() != null) {
+ return holder.woven$getDynamicRecipeRemainder().getRemainder(inv.getStack(slot.get()), this.getId());
+ }
+
+ return new ItemStack(origItem);
+ }
+}
diff --git a/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinBrewingStandBlockEntity.java b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinBrewingStandBlockEntity.java
new file mode 100644
index 0000000..f5a6c09
--- /dev/null
+++ b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinBrewingStandBlockEntity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020 WovenMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.wovenmc.woven.mixin.item.settings;
+
+import net.wovenmc.woven.impl.item.settings.WovenItemSettingsHolder;
+import org.spongepowered.asm.mixin.Mixin;
+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.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import net.minecraft.block.entity.BrewingStandBlockEntity;
+import net.minecraft.item.ItemConvertible;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
+
+@Mixin(BrewingStandBlockEntity.class)
+public abstract class MixinBrewingStandBlockEntity {
+ private static final Identifier BREWING_ID = new Identifier("brewing");
+ private final ThreadLocal stack = new ThreadLocal<>();
+
+ @Inject(method = "craft", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getRecipeRemainder()Lnet/minecraft/item/Item;"),
+ locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+ private void cacheStack(CallbackInfo info, ItemStack stack) {
+ this.stack.set(stack);
+ }
+
+ @Redirect(method = "craft", at = @At(value = "NEW", target = "net/minecraft/item/ItemStack"))
+ private ItemStack getNewRemainder(ItemConvertible origItem) {
+ WovenItemSettingsHolder holder = (WovenItemSettingsHolder) origItem;
+
+ if (holder.woven$getDynamicRecipeRemainder() != null) {
+ return holder.woven$getDynamicRecipeRemainder().getRemainder(stack.get(), BREWING_ID);
+ }
+
+ return new ItemStack(origItem);
+ }
+}
diff --git a/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinItem.java b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinItem.java
new file mode 100644
index 0000000..741af2d
--- /dev/null
+++ b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinItem.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2020 WovenMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.wovenmc.woven.mixin.item.settings;
+
+import net.wovenmc.woven.api.item.settings.EquipmentHandler;
+import net.wovenmc.woven.api.item.settings.RecipeRemainderHandler;
+import net.wovenmc.woven.impl.item.settings.WovenItemSettingsHolder;
+import net.wovenmc.woven.api.item.settings.MeterComponent;
+import net.wovenmc.woven.api.item.settings.WovenItemSettings;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.gen.Accessor;
+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.CallbackInfoReturnable;
+
+import net.minecraft.item.Item;
+
+@Mixin(Item.class)
+public abstract class MixinItem implements WovenItemSettingsHolder {
+ @Unique
+ private MeterComponent woven$meterComponent;
+ @Unique
+ private RecipeRemainderHandler woven$dynamicRecipeRemainder;
+ @Unique
+ private boolean woven$selfRemainder;
+ @Unique
+ private EquipmentHandler woven$equipmentHandler;
+
+ @Inject(method = "", at = @At("RETURN"))
+ private void captureInit(Item.Settings settings, CallbackInfo info) {
+ if (settings instanceof WovenItemSettings) {
+ WovenItemSettings woven = (WovenItemSettings) settings;
+ this.woven$meterComponent = woven.getMeterComponent();
+ this.woven$dynamicRecipeRemainder = woven.getRecipeRemainder();
+ this.woven$selfRemainder = woven.remainsSelf();
+ this.woven$equipmentHandler = woven.getEquipmentHandler();
+ }
+ }
+
+ @Nullable
+ @Override
+ public MeterComponent woven$getMeterComponent() {
+ return woven$meterComponent;
+ }
+
+ @Nullable
+ @Override
+ public RecipeRemainderHandler woven$getDynamicRecipeRemainder() {
+ return woven$dynamicRecipeRemainder;
+ }
+
+ @Nullable
+ @Override
+ public EquipmentHandler woven$getEquipmentHandler() {
+ return woven$equipmentHandler;
+ }
+
+ @Inject(method = "getRecipeRemainder", at = @At("HEAD"))
+ private void injectSelfRemainder(CallbackInfoReturnable- info) {
+ if (woven$selfRemainder) info.setReturnValue((Item) (Object) this);
+ }
+
+ @Mixin(Item.Settings.class)
+ public interface ItemSettingsAccessor {
+ @Accessor
+ Item getRecipeRemainder();
+ }
+}
diff --git a/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinMobEntity.java b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinMobEntity.java
new file mode 100644
index 0000000..2bb1c91
--- /dev/null
+++ b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinMobEntity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020 WovenMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.wovenmc.woven.mixin.item.settings;
+
+import net.wovenmc.woven.impl.item.settings.WovenItemSettingsHolder;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.entity.mob.MobEntity;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+
+@Mixin(MobEntity.class)
+public abstract class MixinMobEntity {
+ @Inject(method = "getPreferredEquipmentSlot", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/item/ItemStack;getItem()Lnet/minecraft/item/Item;"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD)
+ private static void onGetPreferredEquipmentSlot(ItemStack stack, CallbackInfoReturnable info, Item item) {
+ WovenItemSettingsHolder holder = ((WovenItemSettingsHolder) item);
+
+ if (holder.woven$getEquipmentHandler() != null) {
+ info.setReturnValue(holder.woven$getEquipmentHandler().getEquipmentSlot(stack));
+ }
+ }
+}
diff --git a/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinRecipe.java b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinRecipe.java
new file mode 100644
index 0000000..c097672
--- /dev/null
+++ b/src/main/java/net/wovenmc/woven/mixin/item/settings/MixinRecipe.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2020 WovenMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.wovenmc.woven.mixin.item.settings;
+
+import net.wovenmc.woven.impl.item.settings.WovenItemSettingsHolder;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Overwrite;
+import org.spongepowered.asm.mixin.Shadow;
+
+import net.minecraft.inventory.Inventory;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.Recipe;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.collection.DefaultedList;
+
+@Mixin(Recipe.class)
+public interface MixinRecipe {
+ @Shadow
+ Identifier getId();
+
+ /**
+ * @author repulica
+ * @reason injections into interfaces dont work rn
+ * TODO: remove once default interface method injection works
+ */
+ @Overwrite
+ default DefaultedList getRemainingStacks(C inventory) {
+ DefaultedList defaultedList = DefaultedList.ofSize(inventory.size(), ItemStack.EMPTY);
+
+ for (int i = 0; i < defaultedList.size(); ++i) {
+ Item item = inventory.getStack(i).getItem();
+ WovenItemSettingsHolder woven = (WovenItemSettingsHolder) item;
+
+ if (woven.woven$getDynamicRecipeRemainder() != null) {
+ defaultedList.set(i, woven.woven$getDynamicRecipeRemainder().getRemainder(inventory.getStack(i), getId()));
+ } else if (item.hasRecipeRemainder()) {
+ defaultedList.set(i, new ItemStack(item.getRecipeRemainder()));
+ }
+ }
+
+ return defaultedList;
+ }
+
+ /*
+ @Inject(method = "getRemainingStacks", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getRecipeRemainder()Lnet/minecraft/item/Item;"),
+ locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+ default void cacheSlot(C inv, CallbackInfoReturnable> info, DefaultedList ret,
+ int index) {
+ GrossThreadLocalHack.THREADLOCALS.computeIfAbsent((Recipe>)this, x -> new ThreadLocal<>()).set(index);
+ }
+
+ @Redirect(method = "getRemainingStacks", at = @At(value = "NEW", target = "net/minecraft/item/ItemStack"))
+ default ItemStack getNewRemainder(ItemConvertible origItem, C inv) {
+ WovenItemSettingsHolder holder = (WovenItemSettingsHolder) origItem;
+
+ if (holder.woven$getDynamicRecipeRemainder() != null) {
+ return holder.woven$getDynamicRecipeRemainder().apply(inv.getStack(
+ GrossThreadLocalHack.THREADLOCALS.computeIfAbsent(
+ (Recipe>)this, x -> new ThreadLocal<>()).get()), this.getId());
+ }
+
+ return new ItemStack(origItem);
+ }*/
+}
diff --git a/src/main/java/net/wovenmc/woven/mixin/item/settings/client/MixinItemRenderer.java b/src/main/java/net/wovenmc/woven/mixin/item/settings/client/MixinItemRenderer.java
new file mode 100644
index 0000000..ddf0b28
--- /dev/null
+++ b/src/main/java/net/wovenmc/woven/mixin/item/settings/client/MixinItemRenderer.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020 WovenMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.wovenmc.woven.mixin.item.settings.client;
+
+import com.mojang.blaze3d.systems.RenderSystem;
+import net.wovenmc.woven.api.item.settings.MeterComponent;
+import net.wovenmc.woven.impl.item.settings.WovenItemSettingsHolder;
+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.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.client.render.BufferBuilder;
+import net.minecraft.client.render.Tessellator;
+import net.minecraft.client.render.item.ItemRenderer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.math.MathHelper;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+
+@Environment(EnvType.CLIENT)
+@Mixin(ItemRenderer.class)
+public abstract class MixinItemRenderer {
+ @Shadow
+ protected abstract void renderGuiQuad(BufferBuilder buffer, int x, int y, int width, int height, int red, int green, int blue, int alpha);
+
+ @Inject(method = "renderGuiItemOverlay(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;isDamaged()Z"), cancellable = true)
+ private void injectCustomMeter(TextRenderer textRenderer, ItemStack stack, int x, int y, String label,
+ CallbackInfo info) {
+ WovenItemSettingsHolder holder = (WovenItemSettingsHolder) stack.getItem();
+
+ if (holder.woven$getMeterComponent() != null) {
+ MeterComponent component = holder.woven$getMeterComponent();
+ float value = component.getLevel(stack);
+
+ if (value < 1 || (value == 1 && component.displayAtFull())) {
+ //draw the bar
+ RenderSystem.disableDepthTest();
+ RenderSystem.disableTexture();
+ RenderSystem.disableAlphaTest();
+ RenderSystem.disableBlend();
+ Tessellator tessellator = Tessellator.getInstance();
+ BufferBuilder bufferBuilder = tessellator.getBuffer();
+ int length = Math.round(value * 13F);
+ int color = component.getColor(stack);
+ this.renderGuiQuad(bufferBuilder, x + 2, y + 13, 13, 2, 0, 0, 0, 255);
+ this.renderGuiQuad(bufferBuilder, x + 2, y + 13, length, 1, color >> 16 & 255, color >> 8 & 255, color & 255, 255);
+ RenderSystem.enableBlend();
+ RenderSystem.enableAlphaTest();
+ RenderSystem.enableTexture();
+ RenderSystem.enableDepthTest();
+
+ //clean-up for skipped code in ItemRenderer
+ ClientPlayerEntity clientPlayerEntity = MinecraftClient.getInstance().player;
+ float cooldown = clientPlayerEntity == null ? 0.0F : clientPlayerEntity.getItemCooldownManager().getCooldownProgress(stack.getItem(), MinecraftClient.getInstance().getTickDelta());
+
+ if (cooldown > 0.0F) {
+ RenderSystem.disableDepthTest();
+ RenderSystem.disableTexture();
+ RenderSystem.enableBlend();
+ RenderSystem.defaultBlendFunc();
+ Tessellator tesselator = Tessellator.getInstance();
+ BufferBuilder buffer = tesselator.getBuffer();
+ this.renderGuiQuad(buffer, x, y + MathHelper.floor(16.0F * (1.0F - cooldown)), 16, MathHelper.ceil(16.0F * cooldown), 255, 255, 255, 127);
+ RenderSystem.enableTexture();
+ RenderSystem.enableDepthTest();
+ }
+
+ info.cancel();
+ }
+ }
+ }
+}
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 8894ca8..1ff31a7 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
- "id": "${namespace}",
- "name": "Woven Module Template",
+ "id": "woven_item_settings",
+ "name": "Woven Item Settings",
"version": "${version}",
"description": "${description}",
"license": "Apache-2.0",
@@ -16,6 +16,8 @@
"icon": "assets/woven/icon.png",
"environment": "*",
"entrypoints": {
+ "main": [
+ ]
},
"mixins": [
"woven-item-settings.mixins.json"
diff --git a/src/main/resources/woven-item-settings.mixins.json b/src/main/resources/woven-item-settings.mixins.json
index 193b8e4..156de0a 100644
--- a/src/main/resources/woven-item-settings.mixins.json
+++ b/src/main/resources/woven-item-settings.mixins.json
@@ -3,5 +3,16 @@
"package": "net.wovenmc.woven.mixin.item.settings",
"compatibilityLevel": "JAVA_8",
"mixins": [
+ "MixinAbstractFurnaceBlockEntity",
+ "MixinBannerDuplicateRecipe",
+ "MixinBookCloningRecipe",
+ "MixinBrewingStandBlockEntity",
+ "MixinItem",
+ "MixinItem$ItemSettingsAccessor",
+ "MixinMobEntity",
+ "MixinRecipe"
+ ],
+ "client": [
+ "client.MixinItemRenderer"
]
}