diff --git a/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java b/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java index 44b4fae..c27a5a5 100644 --- a/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java +++ b/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java @@ -21,19 +21,35 @@ import org.bukkit.structure.Structure; import com.google.common.base.Enums; import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.util.Util; import world.bentobox.boxed.Boxed; import world.bentobox.boxed.listeners.NewAreaListener; +import world.bentobox.boxed.listeners.NewAreaListener.Item; /** * @author tastybento * */ public class AdminPlaceStructureCommand extends CompositeCommand { - + + private static final String STRUCTURE_FILE = "structures.yml"; + + /** + * Integrity determines how damaged the building should look by randomly skipping blocks to place. + * This value can range from 0 to 1. With 0 removing all blocks and 1 spawning the structure in pristine condition. + */ + private static final float INTEGRITY = 1; + + /** + * The palette index of the structure to use, starting at 0, or -1 to pick a random palette. + */ + private static final int PALETTE = -1; + private StructureRotation sr = StructureRotation.NONE; private Mirror mirror = Mirror.NONE; + private boolean noMobs; public AdminPlaceStructureCommand(CompositeCommand parent) { super(parent, "place"); @@ -54,7 +70,7 @@ public class AdminPlaceStructureCommand extends CompositeCommand { // Initialize sr = StructureRotation.NONE; mirror = Mirror.NONE; - + // Check world if (!((Boxed)getAddon()).inWorld(getWorld())) { user.sendMessage("boxed.admin.place.wrong-world"); @@ -66,6 +82,7 @@ public class AdminPlaceStructureCommand extends CompositeCommand { * 4. place ~ ~ ~ * 5. place ~ ~ ~ ROTATION * 6. place ~ ~ ~ ROTATION MIRROR + * 7. place ~ ~ ~ ROTATION MIRROR NO_MOBS */ // Format is place ~ ~ ~ or coords if (args.isEmpty() || args.size() == 2 || args.size() == 3 || args.size() > 6) { @@ -110,6 +127,14 @@ public class AdminPlaceStructureCommand extends CompositeCommand { Arrays.stream(Mirror.values()).map(Mirror::name).forEach(user::sendRawMessage); return false; } + if (args.size() == 7) { + if (args.get(6).toUpperCase(Locale.ENGLISH).equals("NO_MOBS")) { + noMobs = true; + } else { + user.sendMessage("boxed.admin.place.unknown", TextVariables.LABEL, args.get(6).toUpperCase(Locale.ENGLISH)); + return false; + } + } // Syntax is okay return true; } @@ -122,28 +147,39 @@ public class AdminPlaceStructureCommand extends CompositeCommand { int y = args.size() == 1 || args.get(2).equals("~") ? user.getLocation().getBlockY() : Integer.valueOf(args.get(2).trim()); int z = args.size() == 1 || args.get(3).equals("~") ? user.getLocation().getBlockZ() : Integer.valueOf(args.get(3).trim()); Location spot = new Location(user.getWorld(), x, y, z); - s.place(spot, true, sr, mirror, -1, 1, new Random()); - NewAreaListener.removeJigsaw(spot, s, sr, tag.getKey()); - saveStructure(spot, tag, user, sr, mirror); - return true; + s.place(spot, true, sr, mirror, PALETTE, INTEGRITY, new Random()); + NewAreaListener.removeJigsaw(new Item(tag.getKey(), s, spot, sr, mirror, noMobs)); + boolean result = saveStructure(spot, tag, user, sr, mirror); + if (result) { + user.sendMessage("boxed.admin.place.saved"); + } else { + user.sendMessage("boxed.admin.place.failed"); + } + return result; } - private void saveStructure(Location spot, NamespacedKey tag, User user, StructureRotation sr2, Mirror mirror2) { - getAddon().getIslands().getIslandAt(spot).ifPresent(i -> { + private boolean saveStructure(Location spot, NamespacedKey tag, User user, StructureRotation sr2, Mirror mirror2) { + return getAddon().getIslands().getIslandAt(spot).map(i -> { int xx = spot.getBlockX() - i.getCenter().getBlockX(); int zz = spot.getBlockZ() - i.getCenter().getBlockZ(); - File structures = new File(getAddon().getDataFolder(), "structures.yml"); + File structures = new File(getAddon().getDataFolder(), STRUCTURE_FILE); YamlConfiguration config = new YamlConfiguration(); try { config.load(structures); - String value = tag.getKey() + "," + sr2.name() + "," + mirror2.name(); - config.set(spot.getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH) + "." + xx + "," + spot.getBlockY() + "," + zz, value); + StringBuilder v = new StringBuilder(); + v.append(tag.getKey() + "," + sr2.name() + "," + mirror2.name()); + if (noMobs) { + v.append(" NO_MOBS"); + } + config.set(spot.getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH) + "." + xx + "," + spot.getBlockY() + "," + zz, v.toString()); config.save(structures); } catch (IOException | InvalidConfigurationException e) { - // TODO Auto-generated catch block + // TODO Auto-generated catch block e.printStackTrace(); + return false; } - }); + return true; + }).orElse(false); } @@ -163,6 +199,8 @@ public class AdminPlaceStructureCommand extends CompositeCommand { return Optional.of(Arrays.stream(StructureRotation.values()).map(StructureRotation::name).toList()); } else if (args.size() == 7) { return Optional.of(Arrays.stream(Mirror.values()).map(Mirror::name).toList()); + }else if (args.size() == 8) { + return Optional.of(List.of("NO_MOBS")); } return Optional.of(Collections.emptyList()); } diff --git a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java index eab9f94..893f9bf 100644 --- a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java +++ b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java @@ -79,7 +79,7 @@ public class NewAreaListener implements Listener { private static Random rand = new Random(); private boolean pasting; private static Gson gson = new Gson(); - private record Item(String name, Structure structure, Location location, StructureRotation rot, Mirror mirror) {}; + public record Item(String name, Structure structure, Location location, StructureRotation rot, Mirror mirror, Boolean noMobs) {}; Pair min = new Pair(0,0); Pair max = new Pair(0,0); // Database handler for structure data @@ -181,7 +181,14 @@ public class NewAreaListener implements Listener { } private IslandStructures getIslandStructData(String islandId) { - return this.islandStructureCache.computeIfAbsent(islandId, k -> Objects.requireNonNullElse(handler.loadObject(k), new IslandStructures(islandId))); + // Return from cache if it exists + if (islandStructureCache.containsKey(islandId)) { + return islandStructureCache.get(islandId); + } + // Get from database + IslandStructures struct = handler.objectExists(islandId) ? handler.loadObject(islandId) : new IslandStructures(islandId); + this.islandStructureCache.put(islandId, struct); + return struct; } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @@ -219,6 +226,7 @@ public class NewAreaListener implements Listener { for (String vector : section.getKeys(false)) { StructureRotation rot = StructureRotation.NONE; Mirror mirror = Mirror.NONE; + boolean noMobs = false; String name = section.getString(vector); // Check for rotation String[] split = name.split(","); @@ -229,7 +237,10 @@ public class NewAreaListener implements Listener { } if (split.length == 3) { // Mirror - mirror = Enums.getIfPresent(Mirror.class, split[1].strip().toUpperCase(Locale.ENGLISH)).or(Mirror.NONE); + mirror = Enums.getIfPresent(Mirror.class, split[2].strip().toUpperCase(Locale.ENGLISH)).or(Mirror.NONE); + } + if (split.length == 4) { + noMobs = split[3].strip().toUpperCase(Locale.ENGLISH).equals("NO_MOBS"); } // Load Structure Structure s = Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString("minecraft:" + name)); @@ -244,7 +255,7 @@ public class NewAreaListener implements Listener { int y = Integer.valueOf(value[1].strip()); int z = Integer.valueOf(value[2].strip()) + center.getBlockZ(); Location l = new Location(world, x, y, z); - itemsToBuild.add(new Item(name, s, l, rot, mirror)); + itemsToBuild.add(new Item(name, s, l, rot, mirror, noMobs)); } else { addon.logError("Structure file syntax error: " + vector + ": " + value); } @@ -256,7 +267,7 @@ public class NewAreaListener implements Listener { item.structure().place(item.location(), true, item.rot(), item.mirror(), -1, 1, rand); addon.log(item.name() + " placed at " + item.location().getWorld().getName() + " " + Util.xyz(item.location().toVector())); // Find it - BoundingBox bb = removeJigsaw(item.location(), item.structure(), item.rot(), item.name()); + BoundingBox bb = removeJigsaw(item); // Store it addon.getIslands().getIslandAt(item.location()).map(Island::getUniqueId).ifPresent(id -> { addon.log("Saved " + item.name()); @@ -273,13 +284,15 @@ public class NewAreaListener implements Listener { /** * Removes Jigsaw blocks from a placed structure. Fills underwater ruins with water. - * @param loc - location where the structure was placed - * @param structure - structure that was placed - * @param structureRotation - rotation of structure - * @param key + * @param item - record of what's required * @return the resulting bounding box of the structure */ - public static BoundingBox removeJigsaw(Location loc, Structure structure, StructureRotation structureRotation, String key) { + public static BoundingBox removeJigsaw(Item item) { + Location loc = item.location(); + Structure structure = item.structure(); + StructureRotation structureRotation = item.rot(); + String key = item.name(); + Location otherCorner = switch (structureRotation) { case CLOCKWISE_180 -> loc.clone().add(new Vector(-structure.getSize().getX(), structure.getSize().getY(), -structure.getSize().getZ())); @@ -299,7 +312,7 @@ public class NewAreaListener implements Listener { Block b = loc.getWorld().getBlockAt(x, y, z); if (b.getType().equals(Material.JIGSAW)) { // I would like to read the data from the block and do something with it! - processJigsaw(b, structureRotation); + processJigsaw(b, structureRotation, !item.noMobs()); } else if (b.getType().equals(Material.STRUCTURE_BLOCK)) { processStructureBlock(b); } @@ -338,15 +351,19 @@ public class NewAreaListener implements Listener { } private static final Map BUTCHER_ANIMALS = Map.of(0, EntityType.COW, 1, EntityType.SHEEP, 2, EntityType.PIG); - private static void processJigsaw(Block b, StructureRotation structureRotation) { + private static void processJigsaw(Block b, StructureRotation structureRotation, boolean pasteMobs) { String data = nmsData(b); BoxedJigsawBlock bjb = gson.fromJson(data, BoxedJigsawBlock.class); - //BentoBox.getInstance().logDebug("Jigsaw: " + bjb); - //BentoBox.getInstance().logDebug("FinalState: " + bjb.getFinal_state()); String finalState = correctDirection(bjb.getFinal_state(), structureRotation); - //BentoBox.getInstance().logDebug("FinalState after rotation: " + finalState); BlockData bd = Bukkit.createBlockData(finalState); b.setBlockData(bd); + if (!bjb.getPool().equalsIgnoreCase("minecraft:empty") && pasteMobs) { + spawnMob(b, bjb); + } + } + + private static void spawnMob(Block b, BoxedJigsawBlock bjb) { + // bjb.getPool contains a lot more than just mobs, so we have to filter it to see if any mobs are in there. This list may need to grow in the future EntityType type = switch (bjb.getPool()) { case "minecraft:bastion/mobs/piglin" -> EntityType.PIGLIN; @@ -362,16 +379,19 @@ public class NewAreaListener implements Listener { case "minecraft:village/common/animals" -> BUTCHER_ANIMALS.get(rand.nextInt(3)); default -> null; }; + // Villagers if (bjb.getPool().contains("zombie/villagers")) { type = EntityType.ZOMBIE_VILLAGER; } else if (bjb.getPool().contains("villagers")) { type = EntityType.VILLAGER; } + if (type == null) { + BentoBox.getInstance().logDebug(bjb.getPool()); + } // Spawn it if (type != null && b.getWorld().spawnEntity(b.getRelative(BlockFace.UP).getLocation(), type) != null) { //BentoBox.getInstance().logDebug("Spawned a " + type + " at " + b.getRelative(BlockFace.UP).getLocation()); - } - + } } /** diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 104a5c9..44f1c40 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -23,6 +23,18 @@ boxed: parameters: '[home number]' sethome: parameters: '[home number]' + admin: + place: + description: "Place an area structure" + parameters: " " + use-integers: "&c Coordinated must be integers" + wrong-world: "&c This command can only be used in a Boxed world" + unknown-structure: "&c Cannot place: Unknown structure" + unknown-rotation: "&c Cannot place: Unknown rotation type" + unknown-mirror: "&c Cannot place: Unknown mirror type" + saved: "&a Placed and saved to structures.yml" + failed: "&c Could not be saved to structures.yml. Check console for error" + unknown: "&c Unknown parameter: [label]" island: go: parameters: '[home number]' diff --git a/src/main/resources/structures.yml b/src/main/resources/structures.yml index ef77edd..3ab4c31 100644 --- a/src/main/resources/structures.yml +++ b/src/main/resources/structures.yml @@ -1,4 +1,5 @@ -normal: +# This file is written by the /boxadmin place command +ormal: 0,64,80: village/plains/houses/plains_masons_house_1,CLOCKWISE_90 -38,63,20: ruined_portal/portal_5 3,58,-60: shipwreck/rightsideup_backhalf @@ -15,6 +16,8 @@ normal: 34,69,-29: village/common/iron_golem 41,69,-26: village/common/animals/cat_calico 35,69,-22: village/common/animals/cat_calico + 32,70,65: village/savanna/houses/savanna_temple_1,NONE,NONE + 25,70,72: village/savanna/houses/savanna_small_house_7,COUNTERCLOCKWISE_90,NONE,NO_MOBS 99,72,118: pillager_outpost/feature_cage1 33,72,100: village/desert/houses/desert_farm_1 26,72,100: village/desert/houses/desert_medium_house_1 @@ -33,7 +36,16 @@ normal: -52,72,33: village/snowy/houses/snowy_farm_1,COUNTERCLOCKWISE_90,NONE -28,63,47: village/snowy/snowy_lamp_post_01,COUNTERCLOCKWISE_90,NONE -6,64,52: village/snowy/houses/snowy_small_house_3,COUNTERCLOCKWISE_90,NONE - 40,73,-3: village/savanna/houses/savanna_small_house_1,CLOCKWISE_180 + 60,81,88: village/desert/camel_spawn,NONE,NONE + 64,81,93: village/desert/houses/desert_animal_pen_1,NONE,NONE + 49,81,94: village/desert/houses/desert_large_farm_1,NONE,NONE + -106,45,68: underwater_ruin/big_brick_1,NONE,NONE + -117,45,88: underwater_ruin/big_brick_2,NONE,NONE + 64,78,49: village/common/animals/horses_1,NONE,NONE + 65,78,51: village/common/animals/horses_2,NONE,NONE + 67,78,52: village/common/animals/horses_3,NONE,NONE + 57,70,-90: village/common/animals/horses_5,NONE,NONE + 62,70,-88: village/common/animals/horses_5,NONE,NONE nether: 16,32,0: bastion/bridge/starting_pieces/entrance