|
1 | 1 | package net.imprex.zip.nms.v1_19_R1; |
2 | 2 |
|
3 | 3 | import java.io.ByteArrayInputStream; |
4 | | -import java.io.ByteArrayOutputStream; |
| 4 | +import java.io.IOException; |
5 | 5 | import java.lang.reflect.Method; |
6 | 6 | import java.util.ArrayList; |
| 7 | +import java.util.Arrays; |
| 8 | +import java.util.Iterator; |
7 | 9 | import java.util.List; |
8 | 10 | import java.util.UUID; |
9 | 11 |
|
|
12 | 14 | import org.bukkit.inventory.ItemStack; |
13 | 15 | import org.bukkit.inventory.meta.SkullMeta; |
14 | 16 |
|
| 17 | +import com.google.gson.JsonArray; |
| 18 | +import com.google.gson.JsonElement; |
| 19 | +import com.google.gson.JsonObject; |
15 | 20 | import com.mojang.authlib.GameProfile; |
16 | 21 | import com.mojang.authlib.properties.Property; |
| 22 | +import com.mojang.serialization.DataResult; |
| 23 | +import com.mojang.serialization.Dynamic; |
| 24 | +import com.mojang.serialization.DynamicOps; |
| 25 | +import com.mojang.serialization.JsonOps; |
17 | 26 |
|
| 27 | +import net.imprex.zip.common.BPKey; |
18 | 28 | import net.imprex.zip.common.ReflectionUtil; |
| 29 | +import net.imprex.zip.common.ZIPLogger; |
19 | 30 | import net.imprex.zip.nms.api.NmsManager; |
| 31 | +import net.minecraft.SharedConstants; |
| 32 | +import net.minecraft.core.RegistryAccess; |
20 | 33 | import net.minecraft.nbt.CompoundTag; |
21 | 34 | import net.minecraft.nbt.ListTag; |
22 | 35 | import net.minecraft.nbt.NbtIo; |
| 36 | +import net.minecraft.nbt.NbtOps; |
23 | 37 | import net.minecraft.nbt.Tag; |
| 38 | +import net.minecraft.resources.RegistryOps; |
| 39 | +import net.minecraft.server.MinecraftServer; |
| 40 | +import net.minecraft.util.datafix.DataFixers; |
| 41 | +import net.minecraft.util.datafix.fixes.References; |
24 | 42 |
|
25 | 43 | public class ZipNmsManager implements NmsManager { |
26 | 44 |
|
27 | 45 | private static final Class<?> CRAFTMETASKULL_CLASS = ReflectionUtil.getCraftBukkitClass("inventory.CraftMetaSkull"); |
28 | 46 | private static final Method CRAFTMETASKULL_SET_PROFILE = ReflectionUtil.getMethod(CRAFTMETASKULL_CLASS, |
29 | 47 | "setProfile", GameProfile.class); |
30 | 48 |
|
31 | | - public byte[] nbtToBinary(CompoundTag compound) { |
32 | | - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { |
33 | | - NbtIo.writeCompressed(compound, outputStream); |
34 | | - return outputStream.toByteArray(); |
35 | | - } catch (Exception e) { |
36 | | - e.printStackTrace(); |
37 | | - } |
38 | | - return null; |
39 | | - } |
| 49 | + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); |
40 | 50 |
|
41 | | - public CompoundTag binaryToNBT(byte[] binary) { |
42 | | - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { |
43 | | - return NbtIo.readCompressed(inputStream); |
44 | | - } catch (Exception e) { |
45 | | - e.printStackTrace(); |
46 | | - } |
47 | | - return new CompoundTag(); |
48 | | - } |
| 51 | + @SuppressWarnings("deprecation") |
| 52 | + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); |
| 53 | + |
| 54 | + private static final DynamicOps<Tag> DYNAMIC_OPS_NBT = RegistryOps.create(NbtOps.INSTANCE, DEFAULT_REGISTRY); |
| 55 | + private static final DynamicOps<JsonElement> DYNAMIC_OPS_JSON = RegistryOps.create(JsonOps.INSTANCE, DEFAULT_REGISTRY); |
49 | 56 |
|
50 | 57 | @Override |
51 | | - public byte[] itemstackToBinary(ItemStack[] items) { |
52 | | - CompoundTag inventory = new CompoundTag(); |
53 | | - ListTag list = new ListTag(); |
54 | | - for (ItemStack itemStack : items) { |
55 | | - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); |
56 | | - list.add(craftItem.save(new CompoundTag())); |
| 58 | + public JsonObject itemstackToJsonElement(ItemStack[] items) { |
| 59 | + JsonArray jsonItems = new JsonArray(); |
| 60 | + for (int slot = 0; slot < items.length; slot++) { |
| 61 | + ItemStack item = items[slot]; |
| 62 | + if (item == null || item.getType() == Material.AIR) { |
| 63 | + continue; |
| 64 | + } |
| 65 | + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); |
| 66 | + |
| 67 | + DataResult<JsonElement> result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); |
| 68 | + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); |
| 69 | + |
| 70 | + resultJson.addProperty(BPKey.INVENTORY_SLOT, slot); |
| 71 | + jsonItems.add(resultJson); |
57 | 72 | } |
58 | | - inventory.put("i", list); |
59 | | - return nbtToBinary(inventory); |
| 73 | + |
| 74 | + JsonObject outputJson = new JsonObject(); |
| 75 | + outputJson.addProperty(BPKey.INVENTORY_VERSION, 2); |
| 76 | + outputJson.addProperty(BPKey.INVENTORY_DATA_VERSION, DATA_VERSION); |
| 77 | + outputJson.addProperty(BPKey.INVENTORY_ITEMS_SIZE, items.length); |
| 78 | + outputJson.add(BPKey.INVENTORY_ITEMS, jsonItems); |
| 79 | + return outputJson; |
60 | 80 | } |
61 | 81 |
|
62 | 82 | @Override |
63 | | - public List<ItemStack> binaryToItemStack(byte[] binary) { |
64 | | - CompoundTag nbt = binaryToNBT(binary); |
65 | | - List<ItemStack> items = new ArrayList<>(); |
66 | | - if (nbt.contains("i", 9)) { |
67 | | - ListTag list = nbt.getList("i", 10); |
68 | | - for (Tag base : list) { |
69 | | - if (base instanceof CompoundTag) { |
70 | | - items.add(CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.of((CompoundTag) base))); |
| 83 | + public ItemStack[] jsonElementToItemStack(JsonObject json) { |
| 84 | + // check if current version the same |
| 85 | + if (json.get(BPKey.INVENTORY_VERSION).getAsInt() != 2) { |
| 86 | + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); |
| 87 | + } |
| 88 | + |
| 89 | + int dataVersion = json.get(BPKey.INVENTORY_DATA_VERSION).getAsInt(); |
| 90 | + int itemsSize = json.get(BPKey.INVENTORY_ITEMS_SIZE).getAsInt(); |
| 91 | + |
| 92 | + // convert json into bukkit item |
| 93 | + ItemStack[] items = new ItemStack[itemsSize]; |
| 94 | + Arrays.fill(items, new ItemStack(Material.AIR)); |
| 95 | + |
| 96 | + List<ItemStack> duplicateSlot = null; |
| 97 | + |
| 98 | + JsonArray jsonItems = json.get(BPKey.INVENTORY_ITEMS).getAsJsonArray(); |
| 99 | + for (JsonElement item : jsonItems) { |
| 100 | + Dynamic<JsonElement> dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); |
| 101 | + Dynamic<JsonElement> dynamicItemFixed = DataFixers.getDataFixer() |
| 102 | + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); |
| 103 | + |
| 104 | + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC |
| 105 | + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) |
| 106 | + .getOrThrow(false, error -> {}); |
| 107 | + |
| 108 | + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); |
| 109 | + int slot = item.getAsJsonObject().get(BPKey.INVENTORY_SLOT).getAsInt(); |
| 110 | + |
| 111 | + if (itemsSize <= slot) { |
| 112 | + // something went wrong !? maybe user modified it him self |
| 113 | + ZIPLogger.warn("Slot size was extended from " + itemsSize + " to " + slot + " this shoudt not happen. Do not change the slot number inside the config manuel!?"); |
| 114 | + |
| 115 | + ItemStack[] newItems = new ItemStack[slot + 1]; |
| 116 | + System.arraycopy(items, 0, newItems, 0, items.length); |
| 117 | + Arrays.fill(newItems, items.length, newItems.length, new ItemStack(Material.AIR)); |
| 118 | + items = newItems; |
| 119 | + } |
| 120 | + |
| 121 | + if (items[slot].getType() != Material.AIR) { |
| 122 | + if (duplicateSlot == null) { |
| 123 | + duplicateSlot = new ArrayList<>(); |
71 | 124 | } |
| 125 | + duplicateSlot.add(bukkitItem); |
| 126 | + ZIPLogger.warn("Duplicate item found on slot " + slot + " this shoudt not happen. Do not change the slot number inside the config manuel!?"); |
| 127 | + } else { |
| 128 | + items[slot] = bukkitItem; |
72 | 129 | } |
73 | 130 | } |
| 131 | + |
| 132 | + // fill existing empty slots with duplicate item |
| 133 | + while (duplicateSlot != null && !duplicateSlot.isEmpty()) { |
| 134 | + outher: for (Iterator<ItemStack> iterator = duplicateSlot.iterator(); iterator.hasNext();) { |
| 135 | + ItemStack itemStack = (ItemStack) iterator.next(); |
| 136 | + |
| 137 | + for (int i = 0; i < items.length; i++) { |
| 138 | + if (items[i].getType() == Material.AIR) { |
| 139 | + items[i] = itemStack; |
| 140 | + iterator.remove(); |
| 141 | + break; |
| 142 | + } else if (i == items.length - 1) { |
| 143 | + break outher; |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + // extend slot limit and try again |
| 149 | + if (!duplicateSlot.isEmpty()) { |
| 150 | + int extendedSlots = items.length + duplicateSlot.size(); |
| 151 | + ItemStack[] newItems = new ItemStack[extendedSlots]; |
| 152 | + System.arraycopy(items, 0, newItems, 0, items.length); |
| 153 | + Arrays.fill(newItems, items.length, newItems.length, new ItemStack(Material.AIR)); |
| 154 | + items = newItems; |
| 155 | + } |
| 156 | + } |
| 157 | + |
74 | 158 | return items; |
75 | 159 | } |
| 160 | + |
| 161 | + @Override |
| 162 | + public JsonObject migrateToJsonElement(byte[] binary) { |
| 163 | + CompoundTag compound; |
| 164 | + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { |
| 165 | + compound = NbtIo.readCompressed(inputStream); |
| 166 | + } catch (IOException e) { |
| 167 | + throw new IllegalStateException("Unable to parse binary to nbt", e); |
| 168 | + } |
| 169 | + |
| 170 | + ListTag list = compound.getList("i", 10); |
| 171 | + |
| 172 | + int currentSlot = 0; |
| 173 | + |
| 174 | + JsonArray jsonItems = new JsonArray(); |
| 175 | + for (Tag base : list) { |
| 176 | + if (base instanceof CompoundTag itemTag) { |
| 177 | + String itemType = itemTag.getString("id"); |
| 178 | + if (itemType.equals("minecraft:air")) { |
| 179 | + currentSlot++; |
| 180 | + continue; |
| 181 | + } |
| 182 | + |
| 183 | + Dynamic<Tag> dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); |
| 184 | + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC |
| 185 | + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) |
| 186 | + .getOrThrow(false, error -> {}); |
| 187 | + |
| 188 | + DataResult<JsonElement> result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); |
| 189 | + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); |
| 190 | + |
| 191 | + resultJson.addProperty(BPKey.INVENTORY_SLOT, currentSlot); |
| 192 | + jsonItems.add(resultJson); |
| 193 | + |
| 194 | + currentSlot++; |
| 195 | + } |
| 196 | + } |
| 197 | + |
| 198 | + JsonObject json = new JsonObject(); |
| 199 | + json.addProperty(BPKey.INVENTORY_VERSION, 2); |
| 200 | + json.addProperty(BPKey.INVENTORY_DATA_VERSION, DATA_VERSION); |
| 201 | + json.addProperty(BPKey.INVENTORY_ITEMS_SIZE, list.size()); |
| 202 | + json.add(BPKey.INVENTORY_ITEMS, jsonItems); |
| 203 | + return json; |
| 204 | + } |
76 | 205 |
|
77 | 206 | @Override |
78 | 207 | public void setSkullProfile(SkullMeta meta, String texture) { |
|
0 commit comments