package world.bentobox.bentobox.util; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Banner; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.block.CreatureSpawner; import org.bukkit.block.Sign; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.WallSign; import org.bukkit.block.sign.Side; import org.bukkit.block.sign.SignSide; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock; import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner; import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.hooks.MythicMobsHook; import world.bentobox.bentobox.nms.PasteHandler; /** * A utility class for {@link PasteHandler} * * @author tastybento */ public class DefaultPasteUtil { private static final String MINECRAFT = "minecraft:"; private static final Map BLOCK_CONVERSION = Map.of("sign", "oak_sign", "wall_sign", "oak_wall_sign"); private static final BentoBox plugin; static { plugin = BentoBox.getInstance(); } private DefaultPasteUtil() {} // private constructor to hide the implicit public one. /** * Set the block to the location * * @param island - island * @param location - location * @param bpBlock - blueprint block */ public static CompletableFuture setBlock(Island island, Location location, BlueprintBlock bpBlock) { return Util.getChunkAtAsync(location).thenRun(() -> { Block block = location.getBlock(); // Set the block data - default is AIR BlockData bd = createBlockData(bpBlock); block.setBlockData(bd, false); setBlockState(island, block, bpBlock); // Set biome if (bpBlock.getBiome() != null) { block.setBiome(bpBlock.getBiome()); } }); } /** * Create a block data from the blueprint * * @param block - blueprint block * @return the block data */ public static BlockData createBlockData(BlueprintBlock block) { try { return Material.STONE.createBlockData(); //return Bukkit.createBlockData(block.getBlockData()); } catch (Exception e) { BentoBox.getInstance().logStacktrace(e); return convertBlockData(block); } } /** * Convert the blueprint to block data * * @param block - the blueprint block * @return the block data */ public static BlockData convertBlockData(BlueprintBlock block) { BlockData blockData = Bukkit.createBlockData(Material.AIR); try { for (Map.Entry en : BLOCK_CONVERSION.entrySet()) { if (block.getBlockData().startsWith(MINECRAFT + en.getKey())) { blockData = Bukkit.createBlockData(block.getBlockData().replace(MINECRAFT + en.getKey(), MINECRAFT + en.getValue())); break; } } } catch (IllegalArgumentException e) { // This may happen if the block type is no longer supported by the server plugin.logWarning("Blueprint references materials not supported on this server version."); plugin.logWarning("Load blueprint manually, check and save to fix for this server version."); plugin.logWarning("Failed block data: " + block.getBlockData()); } return blockData; } /** * Handles signs, chests and mob spawner blocks * * @param island - island * @param block - block * @param bpBlock - config */ public static void setBlockState(Island island, Block block, BlueprintBlock bpBlock) { // Get the block state BlockState bs = block.getState(); // Signs if (bs instanceof Sign) { for (Side side : Side.values()) { writeSign(island, block, bpBlock, side); } } // Chests, in general else if (bs instanceof InventoryHolder holder) { Inventory ih = holder.getInventory(); // Double chests are pasted as two blocks so inventory is filled twice. // This code stops over-filling for the first block. bpBlock.getInventory().forEach((slot, item) -> ih.setItem(slot, item)); } // Mob spawners else if (bs instanceof CreatureSpawner spawner) { setSpawner(spawner, bpBlock.getCreatureSpawner()); } // Banners else if (bs instanceof Banner banner && bpBlock.getBannerPatterns() != null) { bpBlock.getBannerPatterns().removeIf(Objects::isNull); banner.setPatterns(bpBlock.getBannerPatterns()); banner.update(true, false); } } /** * Set the spawner setting from the blueprint * * @param spawner - spawner * @param s - blueprint spawner */ public static void setSpawner(CreatureSpawner spawner, BlueprintCreatureSpawner s) { spawner.setSpawnedType(s.getSpawnedType()); spawner.setMaxNearbyEntities(s.getMaxNearbyEntities()); spawner.setMaxSpawnDelay(s.getMaxSpawnDelay()); spawner.setMinSpawnDelay(s.getMinSpawnDelay()); spawner.setDelay(s.getDelay()); spawner.setRequiredPlayerRange(s.getRequiredPlayerRange()); spawner.setSpawnRange(s.getSpawnRange()); spawner.update(true, false); } /** * Spawn the blueprint entities to the location * * @param island - island * @param location - location * @param list - blueprint entities */ public static CompletableFuture setEntity(Island island, Location location, List list) { World world = location.getWorld(); assert world != null; return Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null) .forEach(k -> spawnBlueprintEntity(k, location, island))); } /** * Spawn an entity * @param k the blueprint entity definition * @param location location * @param island island * @return true if Bukkit entity spawned, false if MythicMob entity spawned */ static boolean spawnBlueprintEntity(BlueprintEntity k, Location location, Island island) { if (k.getMythicMobsRecord() != null && plugin.getHooks().getHook("MythicMobs") .filter(mmh -> mmh instanceof MythicMobsHook) .map(mmh -> ((MythicMobsHook) mmh).spawnMythicMob(k.getMythicMobsRecord(), location)) .orElse(false)) { // MythicMob has spawned. return false; } LivingEntity e = (LivingEntity) location.getWorld().spawnEntity(location, k.getType()); if (k.getCustomName() != null) { String customName = k.getCustomName(); if (island != null) { // Parse any placeholders in the entity's name, if the owner's connected (he should) Optional owner = Optional.ofNullable(island.getOwner()).map(User::getInstance) .map(User::getPlayer); if (owner.isPresent()) { // Parse for the player's name first (in case placeholders might need it) customName = customName.replace(TextVariables.NAME, owner.get().getName()); // Now parse the placeholders customName = plugin.getPlaceholdersManager().replacePlaceholders(owner.get(), customName); } } // Actually set the custom name e.setCustomName(customName); } k.configureEntity(e); return true; } /** * Write the lines to the sign at the block * * @param island - island * @param block - block * @param bpSign - BlueprintBlock that is the sign * @param side - the side being written */ public static void writeSign(Island island, final Block block, BlueprintBlock bpSign, Side side) { List lines = bpSign.getSignLines(side); boolean glow = bpSign.isGlowingText(side); BlockFace bf; if (block.getType().name().contains("WALL_SIGN")) { WallSign wallSign = (WallSign) block.getBlockData(); bf = wallSign.getFacing(); } else { org.bukkit.block.data.type.Sign sign = (org.bukkit.block.data.type.Sign) block.getBlockData(); bf = sign.getRotation(); } // Handle spawn sign if (side == Side.FRONT && island != null && !lines.isEmpty() && lines.get(0).equalsIgnoreCase(TextVariables.SPAWN_HERE)) { block.setType(Material.AIR); // Orient to face same direction as sign Location spawnPoint = new Location(block.getWorld(), block.getX() + 0.5D, block.getY(), block.getZ() + 0.5D, Util.blockFaceToFloat(bf.getOppositeFace()), 30F); island.setSpawnPoint(block.getWorld().getEnvironment(), spawnPoint); return; } // Get the name of the player String name = ""; if (island != null) { name = plugin.getPlayers().getName(island.getOwner()); } // Handle locale text for starting sign Sign s = (org.bukkit.block.Sign) block.getState(); SignSide signSide = s.getSide(side); // Sign text must be stored under the addon's name.sign.line0,1,2,3 in the yaml file if (island != null && !lines.isEmpty() && lines.get(0).equalsIgnoreCase(TextVariables.START_TEXT)) { // Get the addon that is operating in this world String addonName = plugin.getIWM().getAddon(island.getWorld()).map(addon -> addon.getDescription().getName().toLowerCase(Locale.ENGLISH)).orElse(""); Optional user = Optional.ofNullable(island.getOwner()).map(User::getInstance); if (user.isPresent()) { for (int i = 0; i < 4; i++) { signSide.setLine(i, Util.translateColorCodes(plugin.getLocalesManager().getOrDefault(user.get(), addonName + ".sign.line" + i, "").replace(TextVariables.NAME, name))); } } } else { // Just paste for (int i = 0; i < 4; i++) { signSide.setLine(i, lines.get(i)); } } signSide.setGlowingText(glow); // Update the sign s.update(); } }