From 41a70332a42f989536a969d17ba731396d234fc5 Mon Sep 17 00:00:00 2001 From: IzzelAliz Date: Wed, 17 Feb 2021 02:29:40 +0800 Subject: [PATCH] Handle forge caps in items properly (#143) Maybe not covering all cases --- arclight-common/bukkit.at | 3 +- .../common/bridge/bukkit/ItemMetaBridge.java | 19 +++ .../common/bridge/item/ItemStackBridge.java | 6 + .../mixin/bukkit/CraftItemStackMixin.java | 51 ++++++ .../mixin/bukkit/CraftMetaItemMixin.java | 153 ++++++++++++++++++ .../mixin/core/command/CommandsMixin.java | 7 +- .../mixin/core/item/ItemStackMixin.java | 22 ++- .../resources/mixins.arclight.bukkit.json | 2 + 8 files changed, 257 insertions(+), 6 deletions(-) create mode 100644 arclight-common/src/main/java/io/izzel/arclight/common/bridge/bukkit/ItemMetaBridge.java create mode 100644 arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftItemStackMixin.java create mode 100644 arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftMetaItemMixin.java diff --git a/arclight-common/bukkit.at b/arclight-common/bukkit.at index e7e49fa9..bf0f279e 100644 --- a/arclight-common/bukkit.at +++ b/arclight-common/bukkit.at @@ -26,4 +26,5 @@ public org/bukkit/craftbukkit/v/inventory/CraftMetaTropicalFishBucket public org/bukkit/craftbukkit/v/inventory/CraftMetaTropicalFishBucket/(Lorg/bukkit/craftbukkit/v/inventory/CraftMetaItem;)V public org/bukkit/craftbukkit/v/inventory/CraftMetaCrossbow/(Lorg/bukkit/craftbukkit/v/inventory/CraftMetaItem;)V public org/bukkit/craftbukkit/v/inventory/CraftMetaSuspiciousStew/(Lorg/bukkit/craftbukkit/v/inventory/CraftMetaItem;)V -public org/spigotmc/ActivationRange$ActivationType/boundingBox \ No newline at end of file +public org/spigotmc/ActivationRange$ActivationType/boundingBox +public org/bukkit/craftbukkit/v/inventory/CraftMetaItem/(Lnet/minecraft/nbt/CompoundNBT;)V \ No newline at end of file diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/bukkit/ItemMetaBridge.java b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/bukkit/ItemMetaBridge.java new file mode 100644 index 00000000..bc2a9129 --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/bukkit/ItemMetaBridge.java @@ -0,0 +1,19 @@ +package io.izzel.arclight.common.bridge.bukkit; + +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.INBT; + +import java.util.Map; + +public interface ItemMetaBridge { + + CompoundNBT bridge$getForgeCaps(); + + void bridge$setForgeCaps(CompoundNBT nbt); + + void bridge$offerUnhandledTags(CompoundNBT nbt); + + Map bridge$getUnhandledTags(); + + void bridge$setUnhandledTags(Map tags); +} diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/item/ItemStackBridge.java b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/item/ItemStackBridge.java index e2883053..7f765e72 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/item/ItemStackBridge.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/item/ItemStackBridge.java @@ -1,6 +1,12 @@ package io.izzel.arclight.common.bridge.item; +import net.minecraft.nbt.CompoundNBT; + public interface ItemStackBridge { void bridge$convertStack(int version); + + CompoundNBT bridge$getForgeCaps(); + + void bridge$setForgeCaps(CompoundNBT caps); } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftItemStackMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftItemStackMixin.java new file mode 100644 index 00000000..f9d30b67 --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftItemStackMixin.java @@ -0,0 +1,51 @@ +package io.izzel.arclight.common.mixin.bukkit; + +import io.izzel.arclight.common.bridge.bukkit.ItemMetaBridge; +import io.izzel.arclight.common.bridge.bukkit.MaterialBridge; +import io.izzel.arclight.common.bridge.item.ItemStackBridge; +import io.izzel.arclight.i18n.conf.MaterialPropertySpec; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import org.bukkit.Material; +import org.bukkit.craftbukkit.v.inventory.CraftItemFactory; +import org.bukkit.craftbukkit.v.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v.inventory.CraftMetaItem; +import org.bukkit.inventory.meta.ItemMeta; +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.CallbackInfoReturnable; + +@Mixin(value = CraftItemStack.class, remap = false) +public abstract class CraftItemStackMixin { + + // @formatter:off + @Shadow static Material getType(ItemStack item) { return null; } + @Shadow static boolean hasItemMeta(ItemStack item) { return false; } + // @formatter:on + + @Inject(method = "getItemMeta(Lnet/minecraft/item/ItemStack;)Lorg/bukkit/inventory/meta/ItemMeta;", + cancellable = true, at = @At("HEAD")) + private static void arclight$offerCaps(ItemStack item, CallbackInfoReturnable cir) { + Material type = getType(item); + if (((MaterialBridge) (Object) type).bridge$getType() != MaterialPropertySpec.MaterialType.VANILLA) { + if (hasItemMeta(item)) { + CraftMetaItem metaItem = new CraftMetaItem(item.getTag()); + ((ItemMetaBridge) metaItem).bridge$offerUnhandledTags(item.getTag()); + ((ItemMetaBridge) metaItem).bridge$setForgeCaps(((ItemStackBridge) (Object) item).bridge$getForgeCaps()); + cir.setReturnValue(metaItem); + } else { + cir.setReturnValue(CraftItemFactory.instance().getItemMeta(getType(item))); + } + } + } + + @Inject(method = "setItemMeta(Lnet/minecraft/item/ItemStack;Lorg/bukkit/inventory/meta/ItemMeta;)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ItemStack;convertStack(I)V")) + private static void arclight$setCaps(ItemStack item, ItemMeta itemMeta, CallbackInfoReturnable cir) { + CompoundNBT forgeCaps = ((ItemMetaBridge) itemMeta).bridge$getForgeCaps(); + if (forgeCaps != null) { + ((ItemStackBridge)(Object) item).bridge$setForgeCaps(forgeCaps.copy()); + } + } +} diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftMetaItemMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftMetaItemMixin.java new file mode 100644 index 00000000..70dcecfb --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftMetaItemMixin.java @@ -0,0 +1,153 @@ +package io.izzel.arclight.common.mixin.bukkit; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import io.izzel.arclight.common.bridge.bukkit.ItemMetaBridge; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.INBT; +import org.apache.commons.codec.binary.Base64; +import org.apache.logging.log4j.LogManager; +import org.bukkit.craftbukkit.v.inventory.CraftMetaItem; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +@Mixin(value = CraftMetaItem.class, remap = false) +public class CraftMetaItemMixin implements ItemMetaBridge { + + // @formatter:off + @Shadow(remap = false) @Final private Map unhandledTags; + // @formatter:on + + private static final Set EXTEND_TAGS = ImmutableSet.of( + "map_is_scaling", + "map", + "CustomPotionEffects", + "Potion", + "CustomPotionColor", + "SkullOwner", + "SkullProfile", + "EntityTag", + "BlockEntityTag", + "title", + "author", + "pages", + "resolved", + "generation", + "Fireworks", + "StoredEnchantments", + "Explosion", + "Recipes", + "BucketVariantTag", + "Charged", + "ChargedProjectiles", + "Effects", + "LodestoneDimension", + "LodestonePos", + "LodestoneTracked" + ); + private CompoundNBT forgeCaps; + + @Override + public CompoundNBT bridge$getForgeCaps() { + return this.forgeCaps; + } + + @Override + public void bridge$setForgeCaps(CompoundNBT nbt) { + this.forgeCaps = nbt; + } + + @Override + public void bridge$offerUnhandledTags(CompoundNBT nbt) { + if (getClass().equals(CraftMetaItem.class)) { + for (String s : nbt.keySet()) { + if (EXTEND_TAGS.contains(s)) { + this.unhandledTags.put(s, nbt.get(s)); + } + } + } + } + + @Override + public Map bridge$getUnhandledTags() { + return this.unhandledTags; + } + + @Override + public void bridge$setUnhandledTags(Map tags) { + this.unhandledTags.putAll(tags); + } + + @Inject(method = "serialize(Lcom/google/common/collect/ImmutableMap$Builder;)Lcom/google/common/collect/ImmutableMap$Builder;", at = @At("RETURN")) + private void arclight$serializeForgeCaps(ImmutableMap.Builder builder, CallbackInfoReturnable> cir) throws IOException { + if (this.forgeCaps != null) { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + CompressedStreamTools.writeCompressed(this.forgeCaps, buf); + builder.put("forgeCaps", Base64.encodeBase64String(buf.toByteArray())); + } + } + + @Inject(method = "clone", locals = LocalCapture.CAPTURE_FAILHARD, at = @At("RETURN")) + private void arclight$cloneTags(CallbackInfoReturnable cir, CraftMetaItem clone) { + if (this.unhandledTags != null) { + ((ItemMetaBridge) clone).bridge$getUnhandledTags().putAll(this.unhandledTags); + } + if (this.forgeCaps != null) { + ((ItemMetaBridge) clone).bridge$setForgeCaps(this.forgeCaps.copy()); + } + } + + @ModifyVariable(method = "applyHash", index = 1, at = @At("RETURN")) + private int arclight$applyForgeCapsHash(int hash) { + return 61 * hash + (this.forgeCaps != null ? this.forgeCaps.hashCode() : 0); + } + + @Inject(method = "equalsCommon", cancellable = true, at = @At("HEAD")) + private void arclight$forgeCapsEquals(CraftMetaItem that, CallbackInfoReturnable cir) { + CompoundNBT forgeCaps = ((ItemMetaBridge) that).bridge$getForgeCaps(); + boolean ret; + if (this.forgeCaps == null) { + ret = forgeCaps != null && forgeCaps.size() != 0; + } else { + ret = forgeCaps == null ? this.forgeCaps.size() != 0 : !this.forgeCaps.equals(forgeCaps); + } + if (ret) { + cir.setReturnValue(false); + } + } + + @Inject(method = "(Ljava/util/Map;)V", at = @At("RETURN")) + private void arclight$extractForgeCaps(Map map, CallbackInfo ci) { + if (map.containsKey("forgeCaps")) { + Object forgeCaps = map.get("forgeCaps"); + try { + ByteArrayInputStream buf = new ByteArrayInputStream(Base64.decodeBase64(forgeCaps.toString())); + this.forgeCaps = CompressedStreamTools.readCompressed(buf); + } catch (IOException e) { + LogManager.getLogger(getClass()).error("Reading forge caps", e); + } + } + } + + @Inject(method = "*", locals = LocalCapture.CAPTURE_FAILSOFT, at = @At("RETURN")) + private void arclight$copyForgeCaps(CallbackInfo ci, CraftMetaItem meta) { + CompoundNBT forgeCaps = ((ItemMetaBridge) meta).bridge$getForgeCaps(); + if (forgeCaps != null) { + this.forgeCaps = forgeCaps.copy(); + } + } +} diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/command/CommandsMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/command/CommandsMixin.java index bce98b22..da5974b0 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/command/CommandsMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/command/CommandsMixin.java @@ -17,6 +17,7 @@ import org.bukkit.event.player.PlayerCommandSendEvent; import org.spigotmc.SpigotConfig; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; @@ -28,12 +29,10 @@ public abstract class CommandsMixin { // @formatter:off @Shadow public abstract int handleCommand(CommandSource source, String command); - @Shadow @Final private CommandDispatcher dispatcher; + @Mutable @Shadow @Final private CommandDispatcher dispatcher; + @Shadow protected abstract void commandSourceNodesToSuggestionNodes(CommandNode rootCommandSource, CommandNode rootSuggestion, CommandSource source, Map, CommandNode> commandNodeToSuggestionNode); // @formatter:on - @Shadow - protected abstract void commandSourceNodesToSuggestionNodes(CommandNode rootCommandSource, CommandNode rootSuggestion, CommandSource source, Map, CommandNode> commandNodeToSuggestionNode); - public void arclight$constructor() { this.dispatcher = new CommandDispatcher<>(); this.dispatcher.setConsumer((context, b, i) -> context.getSource().onCommandComplete(context, b, i)); diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/item/ItemStackMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/item/ItemStackMixin.java index 7b8712a7..7827c2e3 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/item/ItemStackMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/item/ItemStackMixin.java @@ -7,6 +7,8 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraftforge.common.capabilities.CapabilityProvider; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bukkit.craftbukkit.v.event.CraftEventFactory; @@ -25,13 +27,31 @@ import java.util.Random; import java.util.function.Consumer; @Mixin(ItemStack.class) -public abstract class ItemStackMixin implements ItemStackBridge { +public abstract class ItemStackMixin extends CapabilityProvider implements ItemStackBridge { // @formatter:off @Shadow @Deprecated private Item item; @Shadow private int count; + @Shadow(remap = false) private CompoundNBT capNBT; // @formatter:on + protected ItemStackMixin(Class baseClass) { + super(baseClass); + } + + @Override + public CompoundNBT bridge$getForgeCaps() { + return this.serializeCaps(); + } + + @Override + public void bridge$setForgeCaps(CompoundNBT caps) { + this.capNBT = caps; + if (caps != null) { + this.deserializeCaps(caps); + } + } + private static final Logger LOG = LogManager.getLogger("Arclight"); public void convertStack(int version) { diff --git a/arclight-common/src/main/resources/mixins.arclight.bukkit.json b/arclight-common/src/main/resources/mixins.arclight.bukkit.json index a813bab0..0ca63f0a 100644 --- a/arclight-common/src/main/resources/mixins.arclight.bukkit.json +++ b/arclight-common/src/main/resources/mixins.arclight.bukkit.json @@ -19,9 +19,11 @@ "CraftHumanEntityMixin", "CraftInventoryMixin", "CraftItemFactoryMixin", + "CraftItemStackMixin", "CraftLegacyLegacyMixin", "CraftLegacyUtilMixin", "CraftMagicNumbersMixin", + "CraftMetaItemMixin", "CraftServerMixin", "CraftVillagerMixin", "CraftWorldMixin",