Handle forge caps in items properly (#143)

Maybe not covering all cases
This commit is contained in:
IzzelAliz 2021-02-17 02:29:40 +08:00
parent 3964235b8f
commit 41a70332a4
8 changed files with 257 additions and 6 deletions

View File

@ -27,3 +27,4 @@ public org/bukkit/craftbukkit/v/inventory/CraftMetaTropicalFishBucket/<init>(Lor
public org/bukkit/craftbukkit/v/inventory/CraftMetaCrossbow/<init>(Lorg/bukkit/craftbukkit/v/inventory/CraftMetaItem;)V public org/bukkit/craftbukkit/v/inventory/CraftMetaCrossbow/<init>(Lorg/bukkit/craftbukkit/v/inventory/CraftMetaItem;)V
public org/bukkit/craftbukkit/v/inventory/CraftMetaSuspiciousStew/<init>(Lorg/bukkit/craftbukkit/v/inventory/CraftMetaItem;)V public org/bukkit/craftbukkit/v/inventory/CraftMetaSuspiciousStew/<init>(Lorg/bukkit/craftbukkit/v/inventory/CraftMetaItem;)V
public org/spigotmc/ActivationRange$ActivationType/boundingBox public org/spigotmc/ActivationRange$ActivationType/boundingBox
public org/bukkit/craftbukkit/v/inventory/CraftMetaItem/<init>(Lnet/minecraft/nbt/CompoundNBT;)V

View File

@ -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<String, INBT> bridge$getUnhandledTags();
void bridge$setUnhandledTags(Map<String, INBT> tags);
}

View File

@ -1,6 +1,12 @@
package io.izzel.arclight.common.bridge.item; package io.izzel.arclight.common.bridge.item;
import net.minecraft.nbt.CompoundNBT;
public interface ItemStackBridge { public interface ItemStackBridge {
void bridge$convertStack(int version); void bridge$convertStack(int version);
CompoundNBT bridge$getForgeCaps();
void bridge$setForgeCaps(CompoundNBT caps);
} }

View File

@ -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<ItemMeta> 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<Boolean> cir) {
CompoundNBT forgeCaps = ((ItemMetaBridge) itemMeta).bridge$getForgeCaps();
if (forgeCaps != null) {
((ItemStackBridge)(Object) item).bridge$setForgeCaps(forgeCaps.copy());
}
}
}

View File

@ -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<String, INBT> unhandledTags;
// @formatter:on
private static final Set<String> 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<String, INBT> bridge$getUnhandledTags() {
return this.unhandledTags;
}
@Override
public void bridge$setUnhandledTags(Map<String, INBT> 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<String, Object> builder, CallbackInfoReturnable<ImmutableMap.Builder<String, Object>> 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<CraftMetaItem> 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<Boolean> 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 = "<init>(Ljava/util/Map;)V", at = @At("RETURN"))
private void arclight$extractForgeCaps(Map<String, Object> 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 = "<init>*", 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();
}
}
}

View File

@ -17,6 +17,7 @@ import org.bukkit.event.player.PlayerCommandSendEvent;
import org.spigotmc.SpigotConfig; import org.spigotmc.SpigotConfig;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@ -28,12 +29,10 @@ public abstract class CommandsMixin {
// @formatter:off // @formatter:off
@Shadow public abstract int handleCommand(CommandSource source, String command); @Shadow public abstract int handleCommand(CommandSource source, String command);
@Shadow @Final private CommandDispatcher<CommandSource> dispatcher; @Mutable @Shadow @Final private CommandDispatcher<CommandSource> dispatcher;
@Shadow protected abstract void commandSourceNodesToSuggestionNodes(CommandNode<CommandSource> rootCommandSource, CommandNode<ISuggestionProvider> rootSuggestion, CommandSource source, Map<CommandNode<CommandSource>, CommandNode<ISuggestionProvider>> commandNodeToSuggestionNode);
// @formatter:on // @formatter:on
@Shadow
protected abstract void commandSourceNodesToSuggestionNodes(CommandNode<CommandSource> rootCommandSource, CommandNode<ISuggestionProvider> rootSuggestion, CommandSource source, Map<CommandNode<CommandSource>, CommandNode<ISuggestionProvider>> commandNodeToSuggestionNode);
public void arclight$constructor() { public void arclight$constructor() {
this.dispatcher = new CommandDispatcher<>(); this.dispatcher = new CommandDispatcher<>();
this.dispatcher.setConsumer((context, b, i) -> context.getSource().onCommandComplete(context, b, i)); this.dispatcher.setConsumer((context, b, i) -> context.getSource().onCommandComplete(context, b, i));

View File

@ -7,6 +7,8 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; 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.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.bukkit.craftbukkit.v.event.CraftEventFactory; import org.bukkit.craftbukkit.v.event.CraftEventFactory;
@ -25,13 +27,31 @@ import java.util.Random;
import java.util.function.Consumer; import java.util.function.Consumer;
@Mixin(ItemStack.class) @Mixin(ItemStack.class)
public abstract class ItemStackMixin implements ItemStackBridge { public abstract class ItemStackMixin extends CapabilityProvider<ItemStack> implements ItemStackBridge {
// @formatter:off // @formatter:off
@Shadow @Deprecated private Item item; @Shadow @Deprecated private Item item;
@Shadow private int count; @Shadow private int count;
@Shadow(remap = false) private CompoundNBT capNBT;
// @formatter:on // @formatter:on
protected ItemStackMixin(Class<ItemStack> 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"); private static final Logger LOG = LogManager.getLogger("Arclight");
public void convertStack(int version) { public void convertStack(int version) {

View File

@ -19,9 +19,11 @@
"CraftHumanEntityMixin", "CraftHumanEntityMixin",
"CraftInventoryMixin", "CraftInventoryMixin",
"CraftItemFactoryMixin", "CraftItemFactoryMixin",
"CraftItemStackMixin",
"CraftLegacyLegacyMixin", "CraftLegacyLegacyMixin",
"CraftLegacyUtilMixin", "CraftLegacyUtilMixin",
"CraftMagicNumbersMixin", "CraftMagicNumbersMixin",
"CraftMetaItemMixin",
"CraftServerMixin", "CraftServerMixin",
"CraftVillagerMixin", "CraftVillagerMixin",
"CraftWorldMixin", "CraftWorldMixin",