001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.bukkit.schematic; 020 021import com.plotsquared.bukkit.util.BukkitUtil; 022import com.sk89q.jnbt.ByteTag; 023import com.sk89q.jnbt.CompoundTag; 024import com.sk89q.jnbt.ListTag; 025import com.sk89q.jnbt.ShortTag; 026import com.sk89q.jnbt.StringTag; 027import com.sk89q.jnbt.Tag; 028import com.sk89q.worldedit.blocks.BaseItemStack; 029import com.sk89q.worldedit.bukkit.BukkitAdapter; 030import com.sk89q.worldedit.world.item.ItemType; 031import org.bukkit.Bukkit; 032import org.bukkit.ChatColor; 033import org.bukkit.World; 034import org.bukkit.block.Block; 035import org.bukkit.block.Container; 036import org.bukkit.block.Sign; 037import org.bukkit.block.Skull; 038import org.bukkit.enchantments.Enchantment; 039import org.bukkit.inventory.Inventory; 040import org.bukkit.inventory.InventoryHolder; 041import org.bukkit.inventory.ItemStack; 042import org.checkerframework.checker.nullness.qual.NonNull; 043 044import java.util.ArrayList; 045import java.util.HashMap; 046import java.util.List; 047import java.util.Map; 048import java.util.Map.Entry; 049 050public class StateWrapper { 051 052 public org.bukkit.block.BlockState state = null; 053 public CompoundTag tag = null; 054 055 /** 056 * @deprecated in favour of using WE methods for obtaining NBT, specifically by obtaining a 057 * {@link com.sk89q.worldedit.world.block.BaseBlock} and then using {@link com.sk89q.worldedit.world.block.BaseBlock#getNbtData()} 058 */ 059 @Deprecated(forRemoval = true, since = "6.9.0") 060 public StateWrapper(org.bukkit.block.BlockState state) { 061 this.state = state; 062 } 063 064 public StateWrapper(CompoundTag tag) { 065 this.tag = tag; 066 } 067 068 public static String jsonToColourCode(String str) { 069 str = str.replace("{\"extra\":", "").replace("],\"text\":\"\"}", "]") 070 .replace("[{\"color\":\"black\",\"text\":\"", "&0") 071 .replace("[{\"color\":\"dark_blue\",\"text\":\"", "&1") 072 .replace("[{\"color\":\"dark_green\",\"text\":\"", "&2") 073 .replace("[{\"color\":\"dark_aqua\",\"text\":\"", "&3") 074 .replace("[{\"color\":\"dark_red\",\"text\":\"", "&4") 075 .replace("[{\"color\":\"dark_purple\",\"text\":\"", "&5") 076 .replace("[{\"color\":\"gold\",\"text\":\"", "&6") 077 .replace("[{\"color\":\"gray\",\"text\":\"", "&7") 078 .replace("[{\"color\":\"dark_gray\",\"text\":\"", "&8") 079 .replace("[{\"color\":\"blue\",\"text\":\"", "&9") 080 .replace("[{\"color\":\"green\",\"text\":\"", "&a") 081 .replace("[{\"color\":\"aqua\",\"text\":\"", "&b") 082 .replace("[{\"color\":\"red\",\"text\":\"", "&c") 083 .replace("[{\"color\":\"light_purple\",\"text\":\"", "&d") 084 .replace("[{\"color\":\"yellow\",\"text\":\"", "&e") 085 .replace("[{\"color\":\"white\",\"text\":\"", "&f") 086 .replace("[{\"obfuscated\":true,\"text\":\"", "&k") 087 .replace("[{\"bold\":true,\"text\":\"", "&l") 088 .replace("[{\"strikethrough\":true,\"text\":\"", "&m") 089 .replace("[{\"underlined\":true,\"text\":\"", "&n") 090 .replace("[{\"italic\":true,\"text\":\"", "&o").replace("[{\"color\":\"black\",", "&0") 091 .replace("[{\"color\":\"dark_blue\",", "&1") 092 .replace("[{\"color\":\"dark_green\",", "&2") 093 .replace("[{\"color\":\"dark_aqua\",", "&3").replace("[{\"color\":\"dark_red\",", "&4") 094 .replace("[{\"color\":\"dark_purple\",", "&5").replace("[{\"color\":\"gold\",", "&6") 095 .replace("[{\"color\":\"gray\",", "&7").replace("[{\"color\":\"dark_gray\",", "&8") 096 .replace("[{\"color\":\"blue\",", "&9").replace("[{\"color\":\"green\",", "&a") 097 .replace("[{\"color\":\"aqua\",", "&b").replace("[{\"color\":\"red\",", "&c") 098 .replace("[{\"color\":\"light_purple\",", "&d").replace("[{\"color\":\"yellow\",", "&e") 099 .replace("[{\"color\":\"white\",", "&f").replace("[{\"obfuscated\":true,", "&k") 100 .replace("[{\"bold\":true,", "&l").replace("[{\"strikethrough\":true,", "&m") 101 .replace("[{\"underlined\":true,", "&n").replace("[{\"italic\":true,", "&o") 102 .replace("{\"color\":\"black\",\"text\":\"", "&0") 103 .replace("{\"color\":\"dark_blue\",\"text\":\"", "&1") 104 .replace("{\"color\":\"dark_green\",\"text\":\"", "&2") 105 .replace("{\"color\":\"dark_aqua\",\"text\":\"", "&3") 106 .replace("{\"color\":\"dark_red\",\"text\":\"", "&4") 107 .replace("{\"color\":\"dark_purple\",\"text\":\"", "&5") 108 .replace("{\"color\":\"gold\",\"text\":\"", "&6") 109 .replace("{\"color\":\"gray\",\"text\":\"", "&7") 110 .replace("{\"color\":\"dark_gray\",\"text\":\"", "&8") 111 .replace("{\"color\":\"blue\",\"text\":\"", "&9") 112 .replace("{\"color\":\"green\",\"text\":\"", "&a") 113 .replace("{\"color\":\"aqua\",\"text\":\"", "&b") 114 .replace("{\"color\":\"red\",\"text\":\"", "&c") 115 .replace("{\"color\":\"light_purple\",\"text\":\"", "&d") 116 .replace("{\"color\":\"yellow\",\"text\":\"", "&e") 117 .replace("{\"color\":\"white\",\"text\":\"", "&f") 118 .replace("{\"obfuscated\":true,\"text\":\"", "&k") 119 .replace("{\"bold\":true,\"text\":\"", "&l") 120 .replace("{\"strikethrough\":true,\"text\":\"", "&m") 121 .replace("{\"underlined\":true,\"text\":\"", "&n") 122 .replace("{\"italic\":true,\"text\":\"", "&o").replace("{\"color\":\"black\",", "&0") 123 .replace("{\"color\":\"dark_blue\",", "&1").replace("{\"color\":\"dark_green\",", "&2") 124 .replace("{\"color\":\"dark_aqua\",", "&3").replace("{\"color\":\"dark_red\",", "&4") 125 .replace("{\"color\":\"dark_purple\",", "&5").replace("{\"color\":\"gold\",", "&6") 126 .replace("{\"color\":\"gray\",", "&7").replace("{\"color\":\"dark_gray\",", "&8") 127 .replace("{\"color\":\"blue\",", "&9").replace("{\"color\":\"green\",", "&a") 128 .replace("{\"color\":\"aqua\",", "&b").replace("{\"color\":\"red\",", "&c") 129 .replace("{\"color\":\"light_purple\",", "&d").replace("{\"color\":\"yellow\",", "&e") 130 .replace("{\"color\":\"white\",", "&f").replace("{\"obfuscated\":true,", "&k") 131 .replace("{\"bold\":true,", "&l").replace("{\"strikethrough\":true,", "&m") 132 .replace("{\"underlined\":true,", "&n").replace("{\"italic\":true,", "&o") 133 .replace("\"color\":\"black\",\"text\":\"", "&0") 134 .replace("\"color\":\"dark_blue\",\"text\":\"", "&1") 135 .replace("\"color\":\"dark_green\",\"text\":\"", "&2") 136 .replace("\"color\":\"dark_aqua\",\"text\":\"", "&3") 137 .replace("\"color\":\"dark_red\",\"text\":\"", "&4") 138 .replace("\"color\":\"dark_purple\",\"text\":\"", "&5") 139 .replace("\"color\":\"gold\",\"text\":\"", "&6") 140 .replace("\"color\":\"gray\",\"text\":\"", "&7") 141 .replace("\"color\":\"dark_gray\",\"text\":\"", "&8") 142 .replace("\"color\":\"blue\",\"text\":\"", "&9") 143 .replace("\"color\":\"green\",\"text\":\"", "&a") 144 .replace("\"color\":\"aqua\",\"text\":\"", "&b") 145 .replace("\"color\":\"red\",\"text\":\"", "&c") 146 .replace("\"color\":\"light_purple\",\"text\":\"", "&d") 147 .replace("\"color\":\"yellow\",\"text\":\"", "&e") 148 .replace("\"color\":\"white\",\"text\":\"", "&f") 149 .replace("\"obfuscated\":true,\"text\":\"", "&k") 150 .replace("\"bold\":true,\"text\":\"", "&l") 151 .replace("\"strikethrough\":true,\"text\":\"", "&m") 152 .replace("\"underlined\":true,\"text\":\"", "&n") 153 .replace("\"italic\":true,\"text\":\"", "&o").replace("\"color\":\"black\",", "&0") 154 .replace("\"color\":\"dark_blue\",", "&1").replace("\"color\":\"dark_green\",", "&2") 155 .replace("\"color\":\"dark_aqua\",", "&3").replace("\"color\":\"dark_red\",", "&4") 156 .replace("\"color\":\"dark_purple\",", "&5").replace("\"color\":\"gold\",", "&6") 157 .replace("\"color\":\"gray\",", "&7").replace("\"color\":\"dark_gray\",", "&8") 158 .replace("\"color\":\"blue\",", "&9").replace("\"color\":\"green\",", "&a") 159 .replace("\"color\":\"aqua\",", "&b").replace("\"color\":\"red\",", "&c") 160 .replace("\"color\":\"light_purple\",", "&d").replace("\"color\":\"yellow\",", "&e") 161 .replace("\"color\":\"white\",", "&f").replace("\"obfuscated\":true,", "&k") 162 .replace("\"bold\":true,", "&l").replace("\"strikethrough\":true,", "&m") 163 .replace("\"underlined\":true,", "&n").replace("\"italic\":true,", "&o") 164 .replace("[{\"text\":\"", "&0").replace("{\"text\":\"", "&0").replace("\"},", "") 165 .replace("\"}]", "").replace("\"}", ""); 166 str = ChatColor.translateAlternateColorCodes('&', str); 167 return str; 168 } 169 170 /** 171 * Restore the TileEntity data to the given world at the given coordinates. 172 * 173 * @param worldName World name 174 * @param x x position 175 * @param y y position 176 * @param z z position 177 * @return true if successful 178 */ 179 public boolean restoreTag(String worldName, int x, int y, int z) { 180 World world = BukkitUtil.getWorld(worldName); 181 if (world == null) { 182 return false; 183 } 184 return restoreTag(world.getBlockAt(x, y, z)); 185 } 186 187 /** 188 * Restore the TileEntity data to the given block 189 * 190 * @param block Block to restore to 191 * @return true if successful 192 */ 193 @SuppressWarnings("deprecation") // #setLine is needed for Spigot compatibility 194 public boolean restoreTag(@NonNull Block block) { 195 if (this.tag == null) { 196 return false; 197 } 198 org.bukkit.block.BlockState state = block.getState(); 199 switch (getId()) { 200 case "chest", "beacon", "brewingstand", "dispenser", "dropper", "furnace", "hopper", "shulkerbox" -> { 201 if (!(state instanceof Container container)) { 202 return false; 203 } 204 List<Tag> itemsTag = this.tag.getListTag("Items").getValue(); 205 Inventory inv = container.getSnapshotInventory(); 206 for (Tag itemTag : itemsTag) { 207 CompoundTag itemComp = (CompoundTag) itemTag; 208 ItemType type = ItemType.REGISTRY.get(itemComp.getString("id").toLowerCase()); 209 if (type == null) { 210 continue; 211 } 212 int count = itemComp.getByte("Count"); 213 int slot = itemComp.getByte("Slot"); 214 CompoundTag tag = (CompoundTag) itemComp.getValue().get("tag"); 215 BaseItemStack baseItemStack = new BaseItemStack(type, tag, count); 216 ItemStack itemStack = BukkitAdapter.adapt(baseItemStack); 217 inv.setItem(slot, itemStack); 218 } 219 container.update(true, false); 220 return true; 221 } 222 case "sign" -> { 223 if (state instanceof Sign sign) { 224 sign.setLine(0, jsonToColourCode(tag.getString("Text1"))); 225 sign.setLine(1, jsonToColourCode(tag.getString("Text2"))); 226 sign.setLine(2, jsonToColourCode(tag.getString("Text3"))); 227 sign.setLine(3, jsonToColourCode(tag.getString("Text4"))); 228 state.update(true); 229 return true; 230 } 231 return false; 232 } 233 case "skull" -> { 234 if (state instanceof Skull skull) { 235 CompoundTag skullOwner = ((CompoundTag) this.tag.getValue().get("SkullOwner")); 236 if (skullOwner == null) { 237 return true; 238 } 239 String player = skullOwner.getString("Name"); 240 if (player == null || player.isEmpty()) { 241 return false; 242 } 243 try { 244 skull.setOwningPlayer(Bukkit.getOfflinePlayer(player)); 245 skull.update(true); 246 } catch (Exception e) { 247 e.printStackTrace(); 248 } 249 return true; 250 } 251 return false; 252 } 253 } 254 return false; 255 } 256 257 /** 258 * Get a CompoundTag of the contents of a block's inventory (chest, furnace, etc.). 259 * 260 * @deprecated in favour of using WorldEdit methods for obtaining NBT, specifically by obtaining a 261 * {@link com.sk89q.worldedit.world.block.BaseBlock} and then using {@link com.sk89q.worldedit.world.block.BaseBlock#getNbtData()} 262 */ 263 @Deprecated(forRemoval = true, since = "6.9.0") 264 public CompoundTag getTag() { 265 if (this.tag != null) { 266 return this.tag; 267 } 268 if (this.state instanceof InventoryHolder inv) { 269 ItemStack[] contents = inv.getInventory().getContents(); 270 Map<String, Tag> values = new HashMap<>(); 271 values.put("Items", new ListTag(CompoundTag.class, serializeInventory(contents))); 272 return new CompoundTag(values); 273 } 274 return null; 275 } 276 277 public String getId() { 278 String tileid = this.tag.getString("id").toLowerCase(); 279 if (tileid.startsWith("minecraft:")) { 280 tileid = tileid.replace("minecraft:", ""); 281 } 282 return tileid; 283 } 284 285 public List<CompoundTag> serializeInventory(ItemStack[] items) { 286 List<CompoundTag> tags = new ArrayList<>(); 287 for (int i = 0; i < items.length; ++i) { 288 if (items[i] != null) { 289 Map<String, Tag> tagData = serializeItem(items[i]); 290 tagData.put("Slot", new ByteTag((byte) i)); 291 tags.add(new CompoundTag(tagData)); 292 } 293 } 294 return tags; 295 } 296 297 public Map<String, Tag> serializeItem(ItemStack item) { 298 Map<String, Tag> data = new HashMap<>(); 299 data.put("id", new StringTag(item.getType().name())); 300 data.put("Damage", new ShortTag(item.getDurability())); 301 data.put("Count", new ByteTag((byte) item.getAmount())); 302 if (!item.getEnchantments().isEmpty()) { 303 List<CompoundTag> enchantmentList = new ArrayList<>(); 304 for (Entry<Enchantment, Integer> entry : item.getEnchantments().entrySet()) { 305 Map<String, Tag> enchantment = new HashMap<>(); 306 enchantment.put("id", new StringTag(entry.getKey().toString())); 307 enchantment.put("lvl", new ShortTag(entry.getValue().shortValue())); 308 enchantmentList.add(new CompoundTag(enchantment)); 309 } 310 Map<String, Tag> auxData = new HashMap<>(); 311 auxData.put("ench", new ListTag(CompoundTag.class, enchantmentList)); 312 data.put("tag", new CompoundTag(auxData)); 313 } 314 return data; 315 } 316 317}