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.destroystokyo.paper.profile.PlayerProfile; 022import com.destroystokyo.paper.profile.ProfileProperty; 023import com.plotsquared.bukkit.util.BukkitUtil; 024import com.sk89q.jnbt.ByteTag; 025import com.sk89q.jnbt.CompoundTag; 026import com.sk89q.jnbt.ListTag; 027import com.sk89q.jnbt.ShortTag; 028import com.sk89q.jnbt.StringTag; 029import com.sk89q.jnbt.Tag; 030import com.sk89q.worldedit.blocks.BaseItemStack; 031import com.sk89q.worldedit.bukkit.BukkitAdapter; 032import com.sk89q.worldedit.world.item.ItemType; 033import io.papermc.lib.PaperLib; 034import org.apache.logging.log4j.LogManager; 035import org.apache.logging.log4j.Logger; 036import org.bukkit.Bukkit; 037import org.bukkit.ChatColor; 038import org.bukkit.DyeColor; 039import org.bukkit.World; 040import org.bukkit.block.Banner; 041import org.bukkit.block.Block; 042import org.bukkit.block.Container; 043import org.bukkit.block.Sign; 044import org.bukkit.block.Skull; 045import org.bukkit.block.banner.Pattern; 046import org.bukkit.block.banner.PatternType; 047import org.bukkit.enchantments.Enchantment; 048import org.bukkit.inventory.Inventory; 049import org.bukkit.inventory.ItemStack; 050import org.checkerframework.checker.nullness.qual.NonNull; 051 052import java.util.ArrayList; 053import java.util.HashMap; 054import java.util.List; 055import java.util.Map; 056import java.util.Map.Entry; 057import java.util.Objects; 058import java.util.UUID; 059 060public class StateWrapper { 061 062 public CompoundTag tag; 063 064 private boolean paperErrorTextureSent = false; 065 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + StateWrapper.class.getSimpleName()); 066 067 public StateWrapper(CompoundTag tag) { 068 this.tag = tag; 069 } 070 071 public static String jsonToColourCode(String str) { 072 str = str.replace("{\"extra\":", "").replace("],\"text\":\"\"}", "]") 073 .replace("[{\"color\":\"black\",\"text\":\"", "&0") 074 .replace("[{\"color\":\"dark_blue\",\"text\":\"", "&1") 075 .replace("[{\"color\":\"dark_green\",\"text\":\"", "&2") 076 .replace("[{\"color\":\"dark_aqua\",\"text\":\"", "&3") 077 .replace("[{\"color\":\"dark_red\",\"text\":\"", "&4") 078 .replace("[{\"color\":\"dark_purple\",\"text\":\"", "&5") 079 .replace("[{\"color\":\"gold\",\"text\":\"", "&6") 080 .replace("[{\"color\":\"gray\",\"text\":\"", "&7") 081 .replace("[{\"color\":\"dark_gray\",\"text\":\"", "&8") 082 .replace("[{\"color\":\"blue\",\"text\":\"", "&9") 083 .replace("[{\"color\":\"green\",\"text\":\"", "&a") 084 .replace("[{\"color\":\"aqua\",\"text\":\"", "&b") 085 .replace("[{\"color\":\"red\",\"text\":\"", "&c") 086 .replace("[{\"color\":\"light_purple\",\"text\":\"", "&d") 087 .replace("[{\"color\":\"yellow\",\"text\":\"", "&e") 088 .replace("[{\"color\":\"white\",\"text\":\"", "&f") 089 .replace("[{\"obfuscated\":true,\"text\":\"", "&k") 090 .replace("[{\"bold\":true,\"text\":\"", "&l") 091 .replace("[{\"strikethrough\":true,\"text\":\"", "&m") 092 .replace("[{\"underlined\":true,\"text\":\"", "&n") 093 .replace("[{\"italic\":true,\"text\":\"", "&o").replace("[{\"color\":\"black\",", "&0") 094 .replace("[{\"color\":\"dark_blue\",", "&1") 095 .replace("[{\"color\":\"dark_green\",", "&2") 096 .replace("[{\"color\":\"dark_aqua\",", "&3").replace("[{\"color\":\"dark_red\",", "&4") 097 .replace("[{\"color\":\"dark_purple\",", "&5").replace("[{\"color\":\"gold\",", "&6") 098 .replace("[{\"color\":\"gray\",", "&7").replace("[{\"color\":\"dark_gray\",", "&8") 099 .replace("[{\"color\":\"blue\",", "&9").replace("[{\"color\":\"green\",", "&a") 100 .replace("[{\"color\":\"aqua\",", "&b").replace("[{\"color\":\"red\",", "&c") 101 .replace("[{\"color\":\"light_purple\",", "&d").replace("[{\"color\":\"yellow\",", "&e") 102 .replace("[{\"color\":\"white\",", "&f").replace("[{\"obfuscated\":true,", "&k") 103 .replace("[{\"bold\":true,", "&l").replace("[{\"strikethrough\":true,", "&m") 104 .replace("[{\"underlined\":true,", "&n").replace("[{\"italic\":true,", "&o") 105 .replace("{\"color\":\"black\",\"text\":\"", "&0") 106 .replace("{\"color\":\"dark_blue\",\"text\":\"", "&1") 107 .replace("{\"color\":\"dark_green\",\"text\":\"", "&2") 108 .replace("{\"color\":\"dark_aqua\",\"text\":\"", "&3") 109 .replace("{\"color\":\"dark_red\",\"text\":\"", "&4") 110 .replace("{\"color\":\"dark_purple\",\"text\":\"", "&5") 111 .replace("{\"color\":\"gold\",\"text\":\"", "&6") 112 .replace("{\"color\":\"gray\",\"text\":\"", "&7") 113 .replace("{\"color\":\"dark_gray\",\"text\":\"", "&8") 114 .replace("{\"color\":\"blue\",\"text\":\"", "&9") 115 .replace("{\"color\":\"green\",\"text\":\"", "&a") 116 .replace("{\"color\":\"aqua\",\"text\":\"", "&b") 117 .replace("{\"color\":\"red\",\"text\":\"", "&c") 118 .replace("{\"color\":\"light_purple\",\"text\":\"", "&d") 119 .replace("{\"color\":\"yellow\",\"text\":\"", "&e") 120 .replace("{\"color\":\"white\",\"text\":\"", "&f") 121 .replace("{\"obfuscated\":true,\"text\":\"", "&k") 122 .replace("{\"bold\":true,\"text\":\"", "&l") 123 .replace("{\"strikethrough\":true,\"text\":\"", "&m") 124 .replace("{\"underlined\":true,\"text\":\"", "&n") 125 .replace("{\"italic\":true,\"text\":\"", "&o").replace("{\"color\":\"black\",", "&0") 126 .replace("{\"color\":\"dark_blue\",", "&1").replace("{\"color\":\"dark_green\",", "&2") 127 .replace("{\"color\":\"dark_aqua\",", "&3").replace("{\"color\":\"dark_red\",", "&4") 128 .replace("{\"color\":\"dark_purple\",", "&5").replace("{\"color\":\"gold\",", "&6") 129 .replace("{\"color\":\"gray\",", "&7").replace("{\"color\":\"dark_gray\",", "&8") 130 .replace("{\"color\":\"blue\",", "&9").replace("{\"color\":\"green\",", "&a") 131 .replace("{\"color\":\"aqua\",", "&b").replace("{\"color\":\"red\",", "&c") 132 .replace("{\"color\":\"light_purple\",", "&d").replace("{\"color\":\"yellow\",", "&e") 133 .replace("{\"color\":\"white\",", "&f").replace("{\"obfuscated\":true,", "&k") 134 .replace("{\"bold\":true,", "&l").replace("{\"strikethrough\":true,", "&m") 135 .replace("{\"underlined\":true,", "&n").replace("{\"italic\":true,", "&o") 136 .replace("\"color\":\"black\",\"text\":\"", "&0") 137 .replace("\"color\":\"dark_blue\",\"text\":\"", "&1") 138 .replace("\"color\":\"dark_green\",\"text\":\"", "&2") 139 .replace("\"color\":\"dark_aqua\",\"text\":\"", "&3") 140 .replace("\"color\":\"dark_red\",\"text\":\"", "&4") 141 .replace("\"color\":\"dark_purple\",\"text\":\"", "&5") 142 .replace("\"color\":\"gold\",\"text\":\"", "&6") 143 .replace("\"color\":\"gray\",\"text\":\"", "&7") 144 .replace("\"color\":\"dark_gray\",\"text\":\"", "&8") 145 .replace("\"color\":\"blue\",\"text\":\"", "&9") 146 .replace("\"color\":\"green\",\"text\":\"", "&a") 147 .replace("\"color\":\"aqua\",\"text\":\"", "&b") 148 .replace("\"color\":\"red\",\"text\":\"", "&c") 149 .replace("\"color\":\"light_purple\",\"text\":\"", "&d") 150 .replace("\"color\":\"yellow\",\"text\":\"", "&e") 151 .replace("\"color\":\"white\",\"text\":\"", "&f") 152 .replace("\"obfuscated\":true,\"text\":\"", "&k") 153 .replace("\"bold\":true,\"text\":\"", "&l") 154 .replace("\"strikethrough\":true,\"text\":\"", "&m") 155 .replace("\"underlined\":true,\"text\":\"", "&n") 156 .replace("\"italic\":true,\"text\":\"", "&o").replace("\"color\":\"black\",", "&0") 157 .replace("\"color\":\"dark_blue\",", "&1").replace("\"color\":\"dark_green\",", "&2") 158 .replace("\"color\":\"dark_aqua\",", "&3").replace("\"color\":\"dark_red\",", "&4") 159 .replace("\"color\":\"dark_purple\",", "&5").replace("\"color\":\"gold\",", "&6") 160 .replace("\"color\":\"gray\",", "&7").replace("\"color\":\"dark_gray\",", "&8") 161 .replace("\"color\":\"blue\",", "&9").replace("\"color\":\"green\",", "&a") 162 .replace("\"color\":\"aqua\",", "&b").replace("\"color\":\"red\",", "&c") 163 .replace("\"color\":\"light_purple\",", "&d").replace("\"color\":\"yellow\",", "&e") 164 .replace("\"color\":\"white\",", "&f").replace("\"obfuscated\":true,", "&k") 165 .replace("\"bold\":true,", "&l").replace("\"strikethrough\":true,", "&m") 166 .replace("\"underlined\":true,", "&n").replace("\"italic\":true,", "&o") 167 .replace("[{\"text\":\"", "&0").replace("{\"text\":\"", "&0").replace("\"},", "") 168 .replace("\"}]", "").replace("\"}", ""); 169 str = ChatColor.translateAlternateColorCodes('&', str); 170 return str; 171 } 172 173 /** 174 * Restore the TileEntity data to the given world at the given coordinates. 175 * 176 * @param worldName World name 177 * @param x x position 178 * @param y y position 179 * @param z z position 180 * @return true if successful 181 */ 182 public boolean restoreTag(String worldName, int x, int y, int z) { 183 World world = BukkitUtil.getWorld(worldName); 184 if (world == null) { 185 return false; 186 } 187 return restoreTag(world.getBlockAt(x, y, z)); 188 } 189 190 /** 191 * Restore the TileEntity data to the given block 192 * 193 * @param block Block to restore to 194 * @return true if successful 195 */ 196 @SuppressWarnings("deprecation") // #setLine is needed for Spigot compatibility 197 public boolean restoreTag(@NonNull Block block) { 198 if (this.tag == null) { 199 return false; 200 } 201 org.bukkit.block.BlockState state = block.getState(); 202 switch (getId()) { 203 case "chest", "beacon", "brewingstand", "dispenser", "dropper", "furnace", "hopper", "shulkerbox" -> { 204 if (!(state instanceof Container container)) { 205 return false; 206 } 207 List<Tag> itemsTag = this.tag.getListTag("Items").getValue(); 208 Inventory inv = container.getSnapshotInventory(); 209 for (Tag itemTag : itemsTag) { 210 CompoundTag itemComp = (CompoundTag) itemTag; 211 ItemType type = ItemType.REGISTRY.get(itemComp.getString("id").toLowerCase()); 212 if (type == null) { 213 continue; 214 } 215 int count = itemComp.getByte("Count"); 216 int slot = itemComp.getByte("Slot"); 217 CompoundTag tag = (CompoundTag) itemComp.getValue().get("tag"); 218 BaseItemStack baseItemStack = new BaseItemStack(type, tag, count); 219 ItemStack itemStack = BukkitAdapter.adapt(baseItemStack); 220 inv.setItem(slot, itemStack); 221 } 222 container.update(true, false); 223 return true; 224 } 225 case "sign" -> { 226 if (state instanceof Sign sign) { 227 sign.setLine(0, jsonToColourCode(tag.getString("Text1"))); 228 sign.setLine(1, jsonToColourCode(tag.getString("Text2"))); 229 sign.setLine(2, jsonToColourCode(tag.getString("Text3"))); 230 sign.setLine(3, jsonToColourCode(tag.getString("Text4"))); 231 state.update(true); 232 return true; 233 } 234 return false; 235 } 236 case "skull" -> { 237 if (state instanceof Skull skull) { 238 CompoundTag skullOwner = ((CompoundTag) this.tag.getValue().get("SkullOwner")); 239 if (skullOwner == null) { 240 return true; 241 } 242 String player = skullOwner.getString("Name"); 243 244 if (player != null && !player.isEmpty()) { 245 try { 246 skull.setOwningPlayer(Bukkit.getOfflinePlayer(player)); 247 skull.update(true); 248 } catch (Exception e) { 249 e.printStackTrace(); 250 } 251 return true; 252 } 253 254 final CompoundTag properties = (CompoundTag) skullOwner.getValue().get("Properties"); 255 if (properties == null) { 256 return false; 257 } 258 final ListTag textures = properties.getListTag("textures"); 259 if (textures.getValue().isEmpty()) { 260 return false; 261 } 262 final CompoundTag textureCompound = (CompoundTag) textures.getValue().get(0); 263 if (textureCompound == null) { 264 return false; 265 } 266 String textureValue = textureCompound.getString("Value"); 267 if (textureValue == null) { 268 return false; 269 } 270 if (!PaperLib.isPaper()) { 271 if (!paperErrorTextureSent) { 272 paperErrorTextureSent = true; 273 LOGGER.error("Failed to populate skull data in your road schematic - This is a Spigot limitation."); 274 } 275 return false; 276 } 277 final PlayerProfile profile = Bukkit.createProfile(UUID.randomUUID()); 278 profile.setProperty(new ProfileProperty("textures", textureValue)); 279 skull.setPlayerProfile(profile); 280 skull.update(true); 281 return true; 282 283 } 284 return false; 285 } 286 case "banner" -> { 287 if (state instanceof Banner banner) { 288 List<Tag> patterns = this.tag.getListTag("Patterns").getValue(); 289 if (patterns == null || patterns.isEmpty()) { 290 return false; 291 } 292 banner.setPatterns(patterns.stream().map(t -> (CompoundTag) t).map(compoundTag -> { 293 DyeColor color = DyeColor.getByWoolData((byte) compoundTag.getInt("Color")); 294 PatternType patternType = PatternType.getByIdentifier(compoundTag.getString("Pattern")); 295 if (color == null || patternType == null) { 296 return null; 297 } 298 return new Pattern(color, patternType); 299 }).filter(Objects::nonNull).toList()); 300 banner.update(true); 301 return true; 302 } 303 return false; 304 } 305 } 306 return false; 307 } 308 309 public String getId() { 310 String tileid = this.tag.getString("id").toLowerCase(); 311 if (tileid.startsWith("minecraft:")) { 312 tileid = tileid.replace("minecraft:", ""); 313 } 314 return tileid; 315 } 316 317 public List<CompoundTag> serializeInventory(ItemStack[] items) { 318 List<CompoundTag> tags = new ArrayList<>(); 319 for (int i = 0; i < items.length; ++i) { 320 if (items[i] != null) { 321 Map<String, Tag> tagData = serializeItem(items[i]); 322 tagData.put("Slot", new ByteTag((byte) i)); 323 tags.add(new CompoundTag(tagData)); 324 } 325 } 326 return tags; 327 } 328 329 public Map<String, Tag> serializeItem(ItemStack item) { 330 Map<String, Tag> data = new HashMap<>(); 331 data.put("id", new StringTag(item.getType().name())); 332 data.put("Damage", new ShortTag(item.getDurability())); 333 data.put("Count", new ByteTag((byte) item.getAmount())); 334 if (!item.getEnchantments().isEmpty()) { 335 List<CompoundTag> enchantmentList = new ArrayList<>(); 336 for (Entry<Enchantment, Integer> entry : item.getEnchantments().entrySet()) { 337 Map<String, Tag> enchantment = new HashMap<>(); 338 enchantment.put("id", new StringTag(entry.getKey().toString())); 339 enchantment.put("lvl", new ShortTag(entry.getValue().shortValue())); 340 enchantmentList.add(new CompoundTag(enchantment)); 341 } 342 Map<String, Tag> auxData = new HashMap<>(); 343 auxData.put("ench", new ListTag(CompoundTag.class, enchantmentList)); 344 data.put("tag", new CompoundTag(auxData)); 345 } 346 return data; 347 } 348 349}