Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions src/main/java/io/github/cotrin8672/cem/mixin/AnvilMenuMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package io.github.cotrin8672.cem.mixin;

import io.github.cotrin8672.cem.util.AnvilCompatibilityContext;
import io.github.cotrin8672.cem.util.EnchantableBlockMapping;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.inventory.AnvilMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.ItemEnchantments;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(AnvilMenu.class)
public class AnvilMenuMixin {
private static final TagKey<Item> CEM_ENCHANTABLE_BLOCKS_TAG =
TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath("createenchantablemachinery", "enchantable_blocks"));
private static final TagKey<Item> CEM_ALLOW_FORTUNE_SILK_PAIR_TAG =
TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath("createenchantablemachinery", "allow_fortune_silk_pair"));

@Unique
private int cem$compatContextDepth = 0;

@Inject(method = "createResult", at = @At("HEAD"))
private void cem$enterCompatContext(CallbackInfo ci) {
AnvilMenu self = (AnvilMenu) (Object) this;
ItemStack left = self.getSlot(0).getItem();
ItemStack right = self.getSlot(1).getItem();
if (!isCrushingWheelItem(left)) return;
if (!isEnchantmentSource(right)) return;

AnvilCompatibilityContext.push();
this.cem$compatContextDepth++;
}

@Inject(method = "createResult", at = @At("RETURN"))
private void cem$exitCompatContext(CallbackInfo ci) {
if (this.cem$compatContextDepth <= 0) return;
this.cem$compatContextDepth--;
AnvilCompatibilityContext.pop();
}

@Inject(method = "createResult", at = @At("TAIL"))
private void cem$blockStackEnchantingForMachineItems(CallbackInfo ci) {
AnvilMenu self = (AnvilMenu) (Object) this;
Slot resultSlot = self.getSlot(2);

ItemStack left = self.getSlot(0).getItem();
ItemStack right = self.getSlot(1).getItem();
boolean isCrushingWheel = isCrushingWheelItem(left);

if (left.isEmpty()) {
return;
}
if (!isMachineBlockItem(left) && !isCrushingWheel) {
return;
}
if (right.isEmpty()) {
return;
}
if (!isEnchantmentSource(right)) {
return;
}
if (left.getCount() > 1) {
if (!resultSlot.getItem().isEmpty()) {
resultSlot.set(ItemStack.EMPTY);
}
// Prevent stale vanilla cost display ("Too Expensive!") when stack enchanting is blocked.
self.setMaximumCost(0);
return;
}

// Custom exception: allow Fortune + Silk Touch together only for machine block items.
ItemEnchantments leftEnchantments = left.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY);
ItemEnchantments rightEnchantments = right.getOrDefault(DataComponents.STORED_ENCHANTMENTS, ItemEnchantments.EMPTY);
if (rightEnchantments.isEmpty()) {
rightEnchantments = right.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY);
}
if (rightEnchantments.isEmpty()) {
return;
}
if (!isCrushingWheel) {
return;
}

int leftFortune = getEnchantmentLevel(leftEnchantments, Enchantments.FORTUNE);
int leftSilk = getEnchantmentLevel(leftEnchantments, Enchantments.SILK_TOUCH);
int rightFortune = getEnchantmentLevel(rightEnchantments, Enchantments.FORTUNE);
int rightSilk = getEnchantmentLevel(rightEnchantments, Enchantments.SILK_TOUCH);

boolean incomingFortuneOrSilk = rightFortune > 0 || rightSilk > 0;
if (!incomingFortuneOrSilk) {
return;
}

boolean hasCombinedPair = (leftFortune > 0 || rightFortune > 0) && (leftSilk > 0 || rightSilk > 0);
if (!hasCombinedPair) {
return;
}
Holder<Enchantment> fortuneHolder = findEnchantmentHolder(leftEnchantments, rightEnchantments, Enchantments.FORTUNE);
Holder<Enchantment> silkHolder = findEnchantmentHolder(leftEnchantments, rightEnchantments, Enchantments.SILK_TOUCH);
if (fortuneHolder == null || silkHolder == null) {
return;
}

ItemStack output = left.copyWithCount(1);
ItemEnchantments.Mutable merged = new ItemEnchantments.Mutable(leftEnchantments);

int fortuneLevel = combineAnvilLevel(leftFortune, rightFortune, fortuneHolder.value().getMaxLevel());
int silkLevel = combineAnvilLevel(leftSilk, rightSilk, silkHolder.value().getMaxLevel());
if (fortuneLevel > 0) merged.set(fortuneHolder, fortuneLevel);
if (silkLevel > 0) merged.set(silkHolder, silkLevel);

output.set(DataComponents.ENCHANTMENTS, merged.toImmutable());
resultSlot.set(output);

int baseCost = Math.max(1, self.getCost());
self.setMaximumCost(baseCost * 4);
}

private static boolean isMachineBlockItem(ItemStack stack) {
if (stack.is(CEM_ENCHANTABLE_BLOCKS_TAG)) return true;
if (!(stack.getItem() instanceof BlockItem blockItem)) return false;
return EnchantableBlockMapping.getOriginalBlocks().contains(blockItem.getBlock())
|| EnchantableBlockMapping.getEnchantableBlocks().contains(blockItem.getBlock());
}

private static boolean isEnchantmentSource(ItemStack stack) {
ItemEnchantments stored = stack.getOrDefault(DataComponents.STORED_ENCHANTMENTS, ItemEnchantments.EMPTY);
if (!stored.isEmpty()) return true;
ItemEnchantments direct = stack.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY);
return !direct.isEmpty();
}

private static boolean isCrushingWheelItem(ItemStack stack) {
return stack.is(CEM_ALLOW_FORTUNE_SILK_PAIR_TAG);
}

private static int combineAnvilLevel(int existingLevel, int incomingLevel, int maxLevel) {
if (incomingLevel <= 0) return existingLevel;
if (existingLevel == incomingLevel) return Math.min(maxLevel, incomingLevel + 1);
return Math.max(existingLevel, incomingLevel);
}

private static int getEnchantmentLevel(ItemEnchantments enchantments, ResourceKey<Enchantment> key) {
for (Object2IntMap.Entry<Holder<Enchantment>> entry : enchantments.entrySet()) {
if (entry.getKey().is(key)) {
return entry.getIntValue();
}
}
return 0;
}

private static Holder<Enchantment> findEnchantmentHolder(
ItemEnchantments left,
ItemEnchantments right,
ResourceKey<Enchantment> key
) {
for (Object2IntMap.Entry<Holder<Enchantment>> entry : left.entrySet()) {
if (entry.getKey().is(key)) return entry.getKey();
}
for (Object2IntMap.Entry<Holder<Enchantment>> entry : right.entrySet()) {
if (entry.getKey().is(key)) return entry.getKey();
}
return null;
}
}
19 changes: 14 additions & 5 deletions src/main/java/io/github/cotrin8672/cem/mixin/BlockItemMixin.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.github.cotrin8672.cem.mixin;

import io.github.cotrin8672.cem.util.EnchantableBlockMapping;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.context.BlockPlaceContext;
Expand All @@ -14,6 +17,9 @@

@Mixin(BlockItem.class)
public abstract class BlockItemMixin extends Item {
private static final TagKey<Item> CEM_ENCHANTABLE_BLOCKS_TAG =
TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath("createenchantablemachinery", "enchantable_blocks"));

public BlockItemMixin(Properties properties) {
super(properties);
}
Expand All @@ -30,11 +36,14 @@ public BlockItemMixin(Properties properties) {
cancellable = true
)
public void cem$getPlacementState(BlockPlaceContext context, CallbackInfoReturnable<BlockState> cir) {
if (!context.getItemInHand().isEnchanted()) return;
if (!context.getItemInHand().is(CEM_ENCHANTABLE_BLOCKS_TAG)) return;

Block alternativeBlock = EnchantableBlockMapping.getAlternativeBlock(getBlock());
if (alternativeBlock != null && context.getItemInHand().isEnchanted()) {
BlockState blockState = alternativeBlock.getStateForPlacement(context);
BlockState state = blockState != null && this.canPlace(context, blockState) ? blockState : null;
cir.setReturnValue(state);
}
if (alternativeBlock == null) return;

BlockState blockState = alternativeBlock.getStateForPlacement(context);
BlockState state = blockState != null && this.canPlace(context, blockState) ? blockState : null;
cir.setReturnValue(state);
}
}
33 changes: 33 additions & 0 deletions src/main/java/io/github/cotrin8672/cem/mixin/EnchantmentMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.github.cotrin8672.cem.mixin;

import io.github.cotrin8672.cem.util.AnvilCompatibilityContext;
import net.minecraft.core.Holder;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.Enchantments;
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;

@Mixin(Enchantment.class)
public class EnchantmentMixin {
@Inject(method = "areCompatible", at = @At("HEAD"), cancellable = true)
private static void cem$allowIncompatibleInCemAnvil(
Holder<Enchantment> first,
Holder<Enchantment> second,
CallbackInfoReturnable<Boolean> cir
) {
if (!AnvilCompatibilityContext.isActive()) return;
if (first.equals(second)) return;
if (!isFortuneSilkPair(first, second)) return;
cir.setReturnValue(true);
}

private static boolean isFortuneSilkPair(Holder<Enchantment> first, Holder<Enchantment> second) {
boolean firstFortune = first.is(Enchantments.FORTUNE);
boolean firstSilk = first.is(Enchantments.SILK_TOUCH);
boolean secondFortune = second.is(Enchantments.FORTUNE);
boolean secondSilk = second.is(Enchantments.SILK_TOUCH);
return (firstFortune && secondSilk) || (firstSilk && secondFortune);
}
}
15 changes: 12 additions & 3 deletions src/main/java/io/github/cotrin8672/cem/mixin/ItemMixin.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.cotrin8672.cem.mixin;

import io.github.cotrin8672.cem.util.EnchantableBlockMapping;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
Expand All @@ -11,18 +13,25 @@

@Mixin(Item.class)
public class ItemMixin {
private static final TagKey<Item> CEM_ENCHANTABLE_BLOCKS_TAG =
TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath("createenchantablemachinery", "enchantable_blocks"));

@Inject(method = "getEnchantmentValue", at = @At("HEAD"), cancellable = true)
private void cem$getEnchantmentValue(CallbackInfoReturnable<Integer> cir) {
Item item = (Item) (Object) this;
if (item instanceof BlockItem blockItem && EnchantableBlockMapping.getOriginalBlocks().contains(blockItem.getBlock())) {
if (item instanceof BlockItem && isCemEnchantableBlockItem(new ItemStack(item))) {
cir.setReturnValue(14);
}
}

@Inject(method = "isEnchantable", at = @At("HEAD"), cancellable = true)
private void cem$isEnchantable(ItemStack stack, CallbackInfoReturnable<Boolean> cir) {
if (stack.getItem() instanceof BlockItem blockItem && EnchantableBlockMapping.getOriginalBlocks().contains(blockItem.getBlock())) {
if (stack.getItem() instanceof BlockItem && isCemEnchantableBlockItem(stack)) {
cir.setReturnValue(true);
}
}

private static boolean isCemEnchantableBlockItem(ItemStack stack) {
return stack.is(CEM_ENCHANTABLE_BLOCKS_TAG);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.github.cotrin8672.cem.util;

public final class AnvilCompatibilityContext {
private static final ThreadLocal<Integer> DEPTH = ThreadLocal.withInitial(() -> 0);

private AnvilCompatibilityContext() {
}

public static void push() {
DEPTH.set(DEPTH.get() + 1);
}

public static void pop() {
int depth = DEPTH.get();
if (depth <= 1) {
DEPTH.remove();
return;
}
DEPTH.set(depth - 1);
}

public static boolean isActive() {
return DEPTH.get() > 0;
}
}
Loading