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" ] }