Merge pull request #63 from BentoBoxWorld/develop

Release 2.3.1
This commit is contained in:
tastybento 2023-12-03 17:35:05 -08:00 committed by GitHub
commit 48568e8312
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 409 additions and 343 deletions

View File

@ -58,14 +58,14 @@
<!-- Non-minecraft related dependencies --> <!-- Non-minecraft related dependencies -->
<powermock.version>2.0.9</powermock.version> <powermock.version>2.0.9</powermock.version>
<!-- More visible way how to change dependency versions --> <!-- More visible way how to change dependency versions -->
<spigot.version>1.20.1-R0.1-SNAPSHOT</spigot.version> <spigot.version>1.20.2-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>1.24.0</bentobox.version> <bentobox.version>2.0.0-SNAPSHOT</bentobox.version>
<!-- Revision variable removes warning about dynamic version --> <!-- Revision variable removes warning about dynamic version -->
<revision>${build.version}-SNAPSHOT</revision> <revision>${build.version}-SNAPSHOT</revision>
<!-- Do not change unless you want different name for local builds. --> <!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number> <build.number>-LOCAL</build.number>
<!-- This allows to change between versions. --> <!-- This allows to change between versions. -->
<build.version>2.3.0</build.version> <build.version>2.3.1</build.version>
<sonar.projectKey>BentoBoxWorld_Boxed</sonar.projectKey> <sonar.projectKey>BentoBoxWorld_Boxed</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization> <sonar.organization>bentobox-world</sonar.organization>

View File

@ -52,6 +52,11 @@ public class Settings implements WorldSettings {
private String defaultPlayerAction = "go"; private String defaultPlayerAction = "go";
/* Boxed */ /* Boxed */
@ConfigComment("Ignore advancements.")
@ConfigComment("If this is true, advancements will not change the size of the box.")
@ConfigEntry(path = "boxed.ignore-advancements")
private boolean ignoreAdvancements = false;
@ConfigComment("Announce advancements. We recommend you set the game rule `/gamerule announceAdvancements false`") @ConfigComment("Announce advancements. We recommend you set the game rule `/gamerule announceAdvancements false`")
@ConfigComment("but that blocks all new advancement announcements. This setting tells Boxed to broadcast new advancements.") @ConfigComment("but that blocks all new advancement announcements. This setting tells Boxed to broadcast new advancements.")
@ConfigEntry(path = "boxed.broadcast-advancements") @ConfigEntry(path = "boxed.broadcast-advancements")
@ -125,7 +130,7 @@ public class Settings implements WorldSettings {
@ConfigComment("If the number of areas is greater than this number, it will stop players from joining the world.") @ConfigComment("If the number of areas is greater than this number, it will stop players from joining the world.")
@ConfigEntry(path = "world.max-areas") @ConfigEntry(path = "world.max-areas")
private int maxIslands = -1; private int maxIslands = -1;
@ConfigComment("Area height - Lowest is 5.") @ConfigComment("Area height - Lowest is 5.")
@ConfigComment("It is the y coordinate of the bedrock block in the blueprint.") @ConfigComment("It is the y coordinate of the bedrock block in the blueprint.")
@ConfigEntry(path = "world.area-height") @ConfigEntry(path = "world.area-height")
@ -317,7 +322,7 @@ public class Settings implements WorldSettings {
@ConfigComment("Grant these advancements") @ConfigComment("Grant these advancements")
@ConfigEntry(path = "area.reset.on-leave.grant-advancements") @ConfigEntry(path = "area.reset.on-leave.grant-advancements")
private List<String> onLeaveGrantAdvancements = new ArrayList<>(); private List<String> onLeaveGrantAdvancements = new ArrayList<>();
@ConfigComment("Toggles the automatic area creation upon the player's first login on your server.") @ConfigComment("Toggles the automatic area creation upon the player's first login on your server.")
@ConfigComment("If set to true,") @ConfigComment("If set to true,")
@ConfigComment(" * Upon connecting to your server for the first time, the player will be told that") @ConfigComment(" * Upon connecting to your server for the first time, the player will be told that")
@ -1735,4 +1740,18 @@ public class Settings implements WorldSettings {
this.islandHeight = islandHeight; this.islandHeight = islandHeight;
} }
/**
* @return the ignoreAdvancements
*/
public boolean isIgnoreAdvancements() {
return ignoreAdvancements;
}
/**
* @param ignoreAdvancements the ignoreAdvancements to set
*/
public void setIgnoreAdvancements(boolean ignoreAdvancements) {
this.ignoreAdvancements = ignoreAdvancements;
}
} }

View File

@ -84,8 +84,8 @@ public class AdvancementListener implements Listener {
*/ */
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onAdvancement(PlayerAdvancementDoneEvent e) { public void onAdvancement(PlayerAdvancementDoneEvent e) {
// Ignore if player is not in survival // Ignore if player is not in survival or if advancements are being ignored
if (!e.getPlayer().getGameMode().equals(GameMode.SURVIVAL)) { if (!e.getPlayer().getGameMode().equals(GameMode.SURVIVAL) || addon.getSettings().isIgnoreAdvancements()) {
return; return;
} }
// Check if player is in the Boxed worlds // Check if player is in the Boxed worlds
@ -137,6 +137,7 @@ public class AdvancementListener implements Listener {
* @param user - user * @param user - user
*/ */
public void syncAdvancements(User user) { public void syncAdvancements(User user) {
if (addon.getSettings().isIgnoreAdvancements()) return;
Island box = addon.getIslands().getIsland(addon.getOverWorld(), user); Island box = addon.getIslands().getIsland(addon.getOverWorld(), user);
if (box != null) { if (box != null) {
grantAdv(user, addon.getAdvManager().getIsland(box).getAdvancements()); grantAdv(user, addon.getAdvManager().getIsland(box).getAdvancements());
@ -174,7 +175,8 @@ public class AdvancementListener implements Listener {
*/ */
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPortal(PlayerPortalEvent e) { public void onPortal(PlayerPortalEvent e) {
if (!addon.inWorld(e.getPlayer().getWorld()) || !e.getPlayer().getGameMode().equals(GameMode.SURVIVAL)) { if (!addon.inWorld(e.getPlayer().getWorld()) || !e.getPlayer().getGameMode().equals(GameMode.SURVIVAL)
|| addon.getSettings().isIgnoreAdvancements()) {
return; return;
} }
if (e.getCause().equals(TeleportCause.NETHER_PORTAL)) { if (e.getCause().equals(TeleportCause.NETHER_PORTAL)) {
@ -194,7 +196,8 @@ public class AdvancementListener implements Listener {
*/ */
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onMove(PlayerMoveEvent e) { public void onMove(PlayerMoveEvent e) {
if (!addon.getSettings().isNetherGenerate() || !Util.sameWorld(e.getPlayer().getWorld(), addon.getNetherWorld())) { if (!addon.getSettings().isNetherGenerate() || !Util.sameWorld(e.getPlayer().getWorld(), addon.getNetherWorld())
|| addon.getSettings().isIgnoreAdvancements()) {
return; return;
} }
// Nether fortress advancement // Nether fortress advancement
@ -263,6 +266,7 @@ public class AdvancementListener implements Listener {
*/ */
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onTeamLeaveTime(TeamLeaveEvent e) { public void onTeamLeaveTime(TeamLeaveEvent e) {
if (addon.getSettings().isIgnoreAdvancements()) return;
User user = User.getInstance(e.getPlayerUUID()); User user = User.getInstance(e.getPlayerUUID());
if (user != null && addon.getSettings().isOnJoinResetAdvancements() && user.isOnline() if (user != null && addon.getSettings().isOnJoinResetAdvancements() && user.isOnline()
&& addon.getOverWorld().equals(Util.getWorld(user.getWorld()))) { && addon.getOverWorld().equals(Util.getWorld(user.getWorld()))) {
@ -278,6 +282,7 @@ public class AdvancementListener implements Listener {
*/ */
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onFirstTime(IslandNewIslandEvent e) { public void onFirstTime(IslandNewIslandEvent e) {
if (addon.getSettings().isIgnoreAdvancements()) return;
User user = User.getInstance(e.getPlayerUUID()); User user = User.getInstance(e.getPlayerUUID());
if (user != null) { if (user != null) {
clearAndSetAdv(user, addon.getSettings().isOnJoinResetAdvancements(), addon.getSettings().getOnJoinGrantAdvancements()); clearAndSetAdv(user, addon.getSettings().isOnJoinResetAdvancements(), addon.getSettings().getOnJoinGrantAdvancements());

View File

@ -3,7 +3,14 @@ package world.bentobox.boxed.listeners;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.*; import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
@ -20,7 +27,7 @@ import org.bukkit.block.structure.Mirror;
import org.bukkit.block.structure.StructureRotation; import org.bukkit.block.structure.StructureRotation;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -54,34 +61,39 @@ import world.bentobox.boxed.objects.BoxedStructureBlock;
import world.bentobox.boxed.objects.IslandStructures; import world.bentobox.boxed.objects.IslandStructures;
/** /**
* @author tastybento * @author tastybento Place structures in areas after they are created
* Place structures in areas after they are created
*/ */
public class NewAreaListener implements Listener { public class NewAreaListener implements Listener {
/** /**
* Structure record contains the name of the structure, the structure itself, where it was placed and * Structure record contains the name of the structure, the structure itself,
* enums for rotation, mirror, and a flag to paste mobs or not. * where it was placed and enums for rotation, mirror, and a flag to paste mobs
* @param name - name of structure * or not.
*
* @param name - name of structure
* @param structure - Structure object * @param structure - Structure object
* @param location - location where it has been placed * @param location - location where it has been placed
* @param rot - rotation * @param rot - rotation
* @param mirror - mirror setting * @param mirror - mirror setting
* @param noMobs - if false, mobs not pasted * @param noMobs - if false, mobs not pasted
*/ */
public record StructureRecord(String name, Structure structure, Location location, StructureRotation rot, Mirror mirror, Boolean noMobs) {} public record StructureRecord(String name, Structure structure, Location location, StructureRotation rot,
Mirror mirror, Boolean noMobs) {
}
private static final Map<Integer, EntityType> BUTCHER_ANIMALS = Map.of(0, EntityType.COW, 1, EntityType.SHEEP, 2, EntityType.PIG); private static final Map<Integer, EntityType> BUTCHER_ANIMALS = Map.of(0, EntityType.COW, 1, EntityType.SHEEP, 2,
private static final List<BlockFace> CARDINALS = List.of(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST); EntityType.PIG);
private static final List<String> JAR_STRUCTURES = List.of("bee", "pillager", "polar_bear", "axolotl", "allay", "parrot", "frog"); private static final List<BlockFace> CARDINALS = List.of(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST,
private static final List<String> STRUCTURES = List.of("ancient_city", "bastion_remnant", "bastion", BlockFace.WEST);
"buried_treasure", "desert_pyramid", "end_city", private static final List<String> JAR_STRUCTURES = List.of("bee", "pillager", "polar_bear", "axolotl", "allay",
"fortress", "igloo", "jungle_pyramid", "mansion", "mineshaft", "mineshaft_mesa", "parrot", "frog");
"monument", "nether_fossil", "ocean_ruin_cold", "ocean_ruin_warm", "pillager_outpost", private static final List<String> STRUCTURES = List.of("ancient_city", "bastion_remnant", "bastion",
"ruined_portal_desert", "ruined_portal_jungle", "ruined_portal_mountain", "ruined_portal_nether", "buried_treasure", "desert_pyramid", "end_city", "fortress", "igloo", "jungle_pyramid", "mansion",
"ruined_portal_ocean", "ruined_portal_swamp", "ruined_portal", "shipwreck_beached", "mineshaft", "mineshaft_mesa", "monument", "nether_fossil", "ocean_ruin_cold", "ocean_ruin_warm",
"shipwreck", "stronghold", "swamp_hut", "village_desert", "village_plains", "pillager_outpost", "ruined_portal_desert", "ruined_portal_jungle", "ruined_portal_mountain",
"village_savanna", "village_snowy", "village_taiga"); "ruined_portal_nether", "ruined_portal_ocean", "ruined_portal_swamp", "ruined_portal", "shipwreck_beached",
"shipwreck", "stronghold", "swamp_hut", "village_desert", "village_plains", "village_savanna",
"village_snowy", "village_taiga");
private final Boxed addon; private final Boxed addon;
private final File structureFile; private final File structureFile;
private final Queue<StructureRecord> itemsToBuild = new LinkedList<>(); private final Queue<StructureRecord> itemsToBuild = new LinkedList<>();
@ -94,37 +106,35 @@ public class NewAreaListener implements Listener {
private final Database<IslandStructures> handler; private final Database<IslandStructures> handler;
private final Map<String, IslandStructures> islandStructureCache = new HashMap<>(); private final Map<String, IslandStructures> islandStructureCache = new HashMap<>();
/** /**
* @param addon addon * @param addon addon
*/ */
public NewAreaListener(Boxed addon) { public NewAreaListener(Boxed addon) {
this.addon = addon; this.addon = addon;
// Save the default structures file from the jar // Save the default structures file from the jar
addon.saveResource("structures.yml", false); addon.saveResource("structures.yml", false);
// Load the config // Load the config
structureFile = new File(addon.getDataFolder(), "structures.yml"); structureFile = new File(addon.getDataFolder(), "structures.yml");
// Get database ready // Get database ready
handler = new Database<>(addon, IslandStructures.class); handler = new Database<>(addon, IslandStructures.class);
// Try to build something every second // Try to build something every second
runStructurePrinter(addon); runStructurePrinter(addon);
} }
private void runStructurePrinter(Boxed addon2) { private void runStructurePrinter(Boxed addon2) {
Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), this::buildStructure, 20, 20); Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), this::buildStructure, 20, 20);
for (String js : JAR_STRUCTURES) { for (String js : JAR_STRUCTURES) {
addon.saveResource("structures/" + js + ".nbt", false); addon.saveResource("structures/" + js + ".nbt", false);
File structureFile = new File(addon.getDataFolder(), "structures/" + js + ".nbt"); File structureFile = new File(addon.getDataFolder(), "structures/" + js + ".nbt");
try { try {
Structure s = Bukkit.getStructureManager().loadStructure(structureFile); Structure s = Bukkit.getStructureManager().loadStructure(structureFile);
Bukkit.getStructureManager().registerStructure(NamespacedKey.fromString("minecraft:boxed/" + js), s); Bukkit.getStructureManager().registerStructure(NamespacedKey.fromString("minecraft:boxed/" + js), s);
addon.log("Loaded " + js + ".nbt"); addon.log("Loaded " + js + ".nbt");
} catch (IOException e) { } catch (IOException e) {
addon.logError("Error trying to load " + structureFile.getAbsolutePath()); addon.logError("Error trying to load " + structureFile.getAbsolutePath());
addon.getPlugin().logStacktrace(e); addon.getPlugin().logStacktrace(e);
} }
} }
} }
@ -132,398 +142,427 @@ public class NewAreaListener implements Listener {
* Build something in the queue * Build something in the queue
*/ */
private void buildStructure() { private void buildStructure() {
// Only kick off a build if there is something to build and something isn't already being built // Only kick off a build if there is something to build and something isn't
if (!pasting && !itemsToBuild.isEmpty()) { // already being built
// Build item if (!pasting && !itemsToBuild.isEmpty()) {
StructureRecord item = itemsToBuild.poll(); // Build item
placeStructure(item); StructureRecord item = itemsToBuild.poll();
} placeStructure(item);
}
} }
/** /**
* Load known structures from the templates file. * Load known structures from the templates file. This makes them available for
* This makes them available for admins to use in the boxed place file. * admins to use in the boxed place file. If this is not done, then no
* If this is not done, then no structures (templates) will be available. * structures (templates) will be available.
*
* @param event BentoBoxReadyEvent * @param event BentoBoxReadyEvent
*/ */
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBentoBoxReady(BentoBoxReadyEvent event) { public void onBentoBoxReady(BentoBoxReadyEvent event) {
addon.saveResource("templates.yml", false); addon.saveResource("templates.yml", false);
File templateFile = new File(addon.getDataFolder(), "templates.yml"); File templateFile = new File(addon.getDataFolder(), "templates.yml");
if (templateFile.exists()) { if (templateFile.exists()) {
YamlConfiguration loader = YamlConfiguration.loadConfiguration(templateFile); YamlConfiguration loader = YamlConfiguration.loadConfiguration(templateFile);
List<String> list = loader.getStringList("templates"); List<String> list = loader.getStringList("templates");
for (String struct : list) { for (String struct : list) {
if (!struct.endsWith("/")) { if (!struct.endsWith("/")) {
Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString(struct)); Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString(struct));
} }
} }
} }
} }
/** /**
* Track if a place has entered a structure. * Track if a place has entered a structure.
*
* @param e PlayerMoveEvent * @param e PlayerMoveEvent
*/ */
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerMove(PlayerMoveEvent e) { public void onPlayerMove(PlayerMoveEvent e) {
// Ignore head movements // Ignore head movements
if (!addon.inWorld(e.getFrom().getWorld()) || e.getFrom().toVector().equals(e.getTo().toVector())) { if (!addon.inWorld(e.getFrom().getWorld()) || e.getFrom().toVector().equals(e.getTo().toVector())) {
return; return;
} }
// Check where the player is // Check where the player is
addon.getIslands().getIslandAt(e.getTo()).ifPresent(island -> { addon.getIslands().getIslandAt(e.getTo()).ifPresent(island -> {
// See if island is in cache // See if island is in cache
final String islandId = island.getUniqueId(); final String islandId = island.getUniqueId();
IslandStructures is = getIslandStructData(islandId); IslandStructures is = getIslandStructData(islandId);
// Check if player is in any of the structures // Check if player is in any of the structures
Map<BoundingBox, String> structures = e.getTo().getWorld().getEnvironment().equals(Environment.NETHER) ? Map<BoundingBox, String> structures = e.getTo().getWorld().getEnvironment().equals(Environment.NETHER)
is.getNetherStructureBoundingBoxMap():is.getStructureBoundingBoxMap(); ? is.getNetherStructureBoundingBoxMap()
for (Map.Entry<BoundingBox, String> en:structures.entrySet()) { : is.getStructureBoundingBoxMap();
if (en.getKey().contains(e.getTo().toVector())) { for (Map.Entry<BoundingBox, String> en : structures.entrySet()) {
for (String s: STRUCTURES) { if (en.getKey().contains(e.getTo().toVector())) {
if (en.getValue().startsWith(s)) { for (String s : STRUCTURES) {
giveAdvFromCriteria(e.getPlayer(), s); if (en.getValue().startsWith(s)) {
} giveAdvFromCriteria(e.getPlayer(), s);
} }
//STRUCTURES.stream().filter(en.getValue()::startsWith).forEach(s -> giveAdvFromCriteria(e.getPlayer(), s)); }
} // STRUCTURES.stream().filter(en.getValue()::startsWith).forEach(s ->
} // giveAdvFromCriteria(e.getPlayer(), s));
}); }
}
});
} }
/** /**
* Gives a player all the advancements that have string as a named criteria * Gives a player all the advancements that have string as a named criteria
*
* @param player - player * @param player - player
* @param string - criteria * @param string - criteria
*/ */
private void giveAdvFromCriteria(Player player, String string) { private void giveAdvFromCriteria(Player player, String string) {
// Give every advancement that requires a bastion // Give every advancement that requires a bastion
Bukkit.advancementIterator().forEachRemaining(ad -> { Bukkit.advancementIterator().forEachRemaining(ad -> {
if (!player.getAdvancementProgress(ad).isDone()) { if (!player.getAdvancementProgress(ad).isDone()) {
for (String crit: ad.getCriteria()) { for (String crit : ad.getCriteria()) {
if (crit.equals(string)) { if (crit.equals(string)) {
// Set the criteria (it may not complete the advancement completely // Set the criteria (it may not complete the advancement completely
player.getAdvancementProgress(ad).awardCriteria(crit); player.getAdvancementProgress(ad).awardCriteria(crit);
break; break;
} }
} }
} }
}); });
} }
/** /**
* Get all the known island structures for this island * Get all the known island structures for this island
*
* @param islandId - island ID * @param islandId - island ID
* @return IslandStructures * @return IslandStructures
*/ */
private IslandStructures getIslandStructData(String islandId) { private IslandStructures getIslandStructData(String islandId) {
// Return from cache if it exists // Return from cache if it exists
if (islandStructureCache.containsKey(islandId)) { if (islandStructureCache.containsKey(islandId)) {
return islandStructureCache.get(islandId); return islandStructureCache.get(islandId);
} }
// Get from database // Get from database
IslandStructures struct = handler.objectExists(islandId) ? handler.loadObject(islandId) : new IslandStructures(islandId); IslandStructures struct = handler.objectExists(islandId) ? handler.loadObject(islandId)
this.islandStructureCache.put(islandId, struct); : new IslandStructures(islandId);
return struct; this.islandStructureCache.put(islandId, struct);
return struct;
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIslandCreated(IslandCreatedEvent event) { public void onIslandCreated(IslandCreatedEvent event) {
setUpIsland(event.getIsland()); setUpIsland(event.getIsland());
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onIslandReset(IslandResettedEvent event) { public void onIslandReset(IslandResettedEvent event) {
setUpIsland(event.getIsland()); setUpIsland(event.getIsland());
} }
private void setUpIsland(Island island) { private void setUpIsland(Island island) {
// Check if this island is in this game // Check if this island is in this game
if (!(addon.inWorld(island.getWorld()))) { if (!(addon.inWorld(island.getWorld()))) {
return; return;
} }
// Load the latest config so that admins can change it on the fly without reloading // Load the latest config so that admins can change it on the fly without
YamlConfiguration config = YamlConfiguration.loadConfiguration(structureFile); // reloading
Location center = island.getProtectionCenter(); YamlConfiguration config = YamlConfiguration.loadConfiguration(structureFile);
for (String env : config.getKeys(false)) { Location center = island.getProtectionCenter();
Environment e = Enums.getIfPresent(Environment.class, env.toUpperCase(Locale.ENGLISH)).orNull(); for (String env : config.getKeys(false)) {
if (e == null) { Environment e = Enums.getIfPresent(Environment.class, env.toUpperCase(Locale.ENGLISH)).orNull();
addon.logError("Error in structures.yml - unknown environment " + env); if (e == null) {
} else { addon.logError("Error in structures.yml - unknown environment " + env);
place(config.getConfigurationSection(env), center, e); } else {
} place(config.getConfigurationSection(env), center, e);
} }
}
} }
private void place(ConfigurationSection section, Location center, Environment env) { private void place(ConfigurationSection section, Location center, Environment env) {
World world = env.equals(Environment.NORMAL) ? addon.getOverWorld() : addon.getNetherWorld(); World world = env.equals(Environment.NORMAL) ? addon.getOverWorld() : addon.getNetherWorld();
// Loop through the structures in the file - there could be more than one if (world == null) {
for (String vector : section.getKeys(false)) { return;
StructureRotation rot = StructureRotation.NONE; }
Mirror mirror = Mirror.NONE; // Loop through the structures in the file - there could be more than one
boolean noMobs = false; for (String vector : section.getKeys(false)) {
String name = section.getString(vector); StructureRotation rot = StructureRotation.NONE;
// Check for rotation Mirror mirror = Mirror.NONE;
String[] split = name.split(","); boolean noMobs = false;
if (split.length > 1) { String name = section.getString(vector);
// Rotation // Check for rotation
rot = Enums.getIfPresent(StructureRotation.class, split[1].strip().toUpperCase(Locale.ENGLISH)).or(StructureRotation.NONE); String[] split = name.split(",");
name = split[0]; if (split.length > 1) {
} // Rotation
if (split.length == 3) { rot = Enums.getIfPresent(StructureRotation.class, split[1].strip().toUpperCase(Locale.ENGLISH))
// Mirror .or(StructureRotation.NONE);
mirror = Enums.getIfPresent(Mirror.class, split[2].strip().toUpperCase(Locale.ENGLISH)).or(Mirror.NONE); name = split[0];
} }
if (split.length == 4) { if (split.length == 3) {
noMobs = split[3].strip().toUpperCase(Locale.ENGLISH).equals("NO_MOBS"); // Mirror
} mirror = Enums.getIfPresent(Mirror.class, split[2].strip().toUpperCase(Locale.ENGLISH)).or(Mirror.NONE);
// Load Structure }
Structure s = Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString("minecraft:" + name)); if (split.length == 4) {
if (s == null) { noMobs = split[3].strip().toUpperCase(Locale.ENGLISH).equals("NO_MOBS");
BentoBox.getInstance().logError("Could not load " + name); }
return; // Load Structure
} Structure s = Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString("minecraft:" + name));
// Extract coords if (s == null) {
String[] value = vector.split(","); BentoBox.getInstance().logError("Could not load " + name);
if (value.length > 2) { return;
int x = Integer.parseInt(value[0].strip()) + center.getBlockX(); }
int y = Integer.parseInt(value[1].strip()); // Extract coords
int z = Integer.parseInt(value[2].strip()) + center.getBlockZ(); String[] value = vector.split(",");
Location l = new Location(world, x, y, z); if (value.length > 2) {
itemsToBuild.add(new StructureRecord(name, s, l, rot, mirror, noMobs)); int x = Integer.parseInt(value[0].strip()) + center.getBlockX();
} else { int y = Integer.parseInt(value[1].strip());
addon.logError("Structure file syntax error: " + vector + ": " + Arrays.toString(value)); int z = Integer.parseInt(value[2].strip()) + center.getBlockZ();
} Location l = new Location(world, x, y, z);
} itemsToBuild.add(new StructureRecord(name, s, l, rot, mirror, noMobs));
} else {
addon.logError("Structure file syntax error: " + vector + ": " + Arrays.toString(value));
}
}
} }
private void placeStructure(StructureRecord item) { private void placeStructure(StructureRecord item) {
// Set the semaphore - only paste one at a time // Set the semaphore - only paste one at a time
pasting = true; pasting = true;
// Place the structure // Place the structure
item.structure().place(item.location(), true, item.rot(), item.mirror(), -1, 1, rand); 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())); addon.log(item.name() + " placed at " + item.location().getWorld().getName() + " "
// Remove any jigsaw artifacts + Util.xyz(item.location().toVector()));
BoundingBox bb = removeJigsaw(item); // Remove any jigsaw artifacts
// Store it for future reference BoundingBox bb = removeJigsaw(item);
addon.getIslands().getIslandAt(item.location()).map(Island::getUniqueId).ifPresent(id -> { // Store it for future reference
addon.log("Saved " + item.name()); addon.getIslands().getIslandAt(item.location()).map(Island::getUniqueId).ifPresent(id -> {
if (item.location().getWorld().getEnvironment().equals(Environment.NETHER)) { addon.log("Saved " + item.name());
getIslandStructData(id).addNetherStructure(bb, item.name()); if (item.location().getWorld().getEnvironment().equals(Environment.NETHER)) {
} else { getIslandStructData(id).addNetherStructure(bb, item.name());
getIslandStructData(id).addStructure(bb, item.name()); } else {
} getIslandStructData(id).addStructure(bb, item.name());
handler.saveObjectAsync(getIslandStructData(id)); }
}); handler.saveObjectAsync(getIslandStructData(id));
// Clear the semaphore });
pasting = false; // Clear the semaphore
pasting = false;
} }
/** /**
* Removes Jigsaw blocks from a placed structure. Fills underwater ruins with water. * Removes Jigsaw blocks from a placed structure. Fills underwater ruins with
* water.
*
* @param item - record of what's required * @param item - record of what's required
* @return the resulting bounding box of the structure * @return the resulting bounding box of the structure
*/ */
public static BoundingBox removeJigsaw(StructureRecord item) { public static BoundingBox removeJigsaw(StructureRecord item) {
Location loc = item.location(); Location loc = item.location();
Structure structure = item.structure(); Structure structure = item.structure();
StructureRotation structureRotation = item.rot(); StructureRotation structureRotation = item.rot();
String key = item.name(); String key = item.name();
Location otherCorner = switch (structureRotation) { Location otherCorner = switch (structureRotation) {
case CLOCKWISE_180 -> loc.clone().add(new Vector(-structure.getSize().getX(), structure.getSize().getY(), -structure.getSize().getZ())); case CLOCKWISE_180 -> loc.clone()
.add(new Vector(-structure.getSize().getX(), structure.getSize().getY(), -structure.getSize().getZ()));
case CLOCKWISE_90 -> loc.clone().add(new Vector(-structure.getSize().getZ(), structure.getSize().getY(), structure.getSize().getX())); case CLOCKWISE_90 -> loc.clone()
.add(new Vector(-structure.getSize().getZ(), structure.getSize().getY(), structure.getSize().getX()));
case COUNTERCLOCKWISE_90 -> loc.clone().add(new Vector(structure.getSize().getZ(), structure.getSize().getY(), -structure.getSize().getX())); case COUNTERCLOCKWISE_90 -> loc.clone()
.add(new Vector(structure.getSize().getZ(), structure.getSize().getY(), -structure.getSize().getX()));
case NONE -> loc.clone().add(new Vector(structure.getSize().getX(), structure.getSize().getY(), structure.getSize().getZ())); case NONE -> loc.clone()
.add(new Vector(structure.getSize().getX(), structure.getSize().getY(), structure.getSize().getZ()));
}; };
BoundingBox bb = BoundingBox.of(loc, otherCorner); BoundingBox bb = BoundingBox.of(loc, otherCorner);
for (int x = (int) bb.getMinX(); x <= bb.getMaxX(); x++) { for (int x = (int) bb.getMinX(); x <= bb.getMaxX(); x++) {
for (int y = (int) bb.getMinY(); y <= bb.getMaxY(); y++) { for (int y = (int) bb.getMinY(); y <= bb.getMaxY(); y++) {
for (int z = (int) bb.getMinZ(); z <= bb.getMaxZ(); z++) { for (int z = (int) bb.getMinZ(); z <= bb.getMaxZ(); z++) {
Block b = loc.getWorld().getBlockAt(x, y, z); Block b = loc.getWorld().getBlockAt(x, y, z);
if (b.getType().equals(Material.JIGSAW)) { if (b.getType().equals(Material.JIGSAW)) {
// I would like to read the data from the block and do something with it! // I would like to read the data from the block and do something with it!
processJigsaw(b, structureRotation, !item.noMobs()); processJigsaw(b, structureRotation, !item.noMobs());
} else if (b.getType().equals(Material.STRUCTURE_BLOCK)) { } else if (b.getType().equals(Material.STRUCTURE_BLOCK)) {
processStructureBlock(b); processStructureBlock(b);
} }
// Set water blocks for underwater ruins // Set water blocks for underwater ruins
if (key.contains("underwater_ruin") && b.getType().equals(Material.AIR)) { if (key.contains("underwater_ruin") && b.getType().equals(Material.AIR)) {
b.setType(Material.WATER); b.setType(Material.WATER);
} }
} }
} }
} }
return bb; return bb;
} }
/** /**
* Process a structure block. Sets it to a structure void at a minimum. * Process a structure block. Sets it to a structure void at a minimum. If the
* If the structure block has metadata indicating it is a chest, then it will fill * structure block has metadata indicating it is a chest, then it will fill the
* the chest with a buried treasure loot. If it is waterlogged, then it will change * chest with a buried treasure loot. If it is waterlogged, then it will change
* the void to water. * the void to water.
*
* @param b structure block * @param b structure block
*/ */
private static void processStructureBlock(Block b) { private static void processStructureBlock(Block b) {
// I would like to read the data from the block and do something with it! // I would like to read the data from the block and do something with it!
String data = nmsData(b); String data = nmsData(b);
BoxedStructureBlock bsb = gson.fromJson(data, BoxedStructureBlock.class); BoxedStructureBlock bsb = gson.fromJson(data, BoxedStructureBlock.class);
b.setType(Material.STRUCTURE_VOID); b.setType(Material.STRUCTURE_VOID);
Enums.getIfPresent(EntityType.class, bsb.getMetadata().toUpperCase(Locale.ENGLISH)).toJavaUtil() Enums.getIfPresent(EntityType.class, bsb.getMetadata().toUpperCase(Locale.ENGLISH)).toJavaUtil()
.ifPresent(type -> b.getWorld().spawnEntity(b.getRelative(BlockFace.UP).getLocation(), type)); .ifPresent(type -> b.getWorld().spawnEntity(b.getRelative(BlockFace.UP).getLocation(), type));
if (bsb.getMetadata().contains("chest")) { if (bsb.getMetadata().contains("chest")) {
Block downBlock = b.getRelative(BlockFace.DOWN); Block downBlock = b.getRelative(BlockFace.DOWN);
if (downBlock.getType().equals(Material.CHEST)) { if (downBlock.getType().equals(Material.CHEST)) {
Chest chest = (Chest)downBlock.getState(); Chest chest = (Chest) downBlock.getState();
// TODO: for now just give treasure // TODO: for now just give treasure
chest.setLootTable(LootTables.BURIED_TREASURE.getLootTable()); chest.setLootTable(LootTables.BURIED_TREASURE.getLootTable());
chest.update(); chest.update();
if (chest.getBlockData() instanceof Waterlogged wl) { if (chest.getBlockData() instanceof Waterlogged wl) {
if (wl.isWaterlogged()) { if (wl.isWaterlogged()) {
b.setType(Material.WATER); b.setType(Material.WATER);
} }
} }
} }
} }
} }
private static void processJigsaw(Block b, StructureRotation structureRotation, boolean pasteMobs) { private static void processJigsaw(Block b, StructureRotation structureRotation, boolean pasteMobs) {
String data = nmsData(b); String data = nmsData(b);
BoxedJigsawBlock bjb = gson.fromJson(data, BoxedJigsawBlock.class); BoxedJigsawBlock bjb = gson.fromJson(data, BoxedJigsawBlock.class);
String finalState = correctDirection(bjb.getFinal_state(), structureRotation); String finalState = correctDirection(bjb.getFinal_state(), structureRotation);
BlockData bd = Bukkit.createBlockData(finalState); BlockData bd = Bukkit.createBlockData(finalState);
b.setBlockData(bd); b.setBlockData(bd);
if (!bjb.getPool().equalsIgnoreCase("minecraft:empty") && pasteMobs) { if (!bjb.getPool().equalsIgnoreCase("minecraft:empty") && pasteMobs) {
spawnMob(b, bjb); spawnMob(b, bjb);
} }
} }
private static void spawnMob(Block b, BoxedJigsawBlock 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 // bjb.getPool contains a lot more than just mobs, so we have to filter it to
EntityType type = // see if any mobs are in there. This list may need to grow in the future
switch (bjb.getPool()) { EntityType type = switch (bjb.getPool()) {
case "minecraft:bastion/mobs/piglin" -> EntityType.PIGLIN; case "minecraft:bastion/mobs/piglin" -> EntityType.PIGLIN;
case "minecraft:bastion/mobs/hoglin" -> EntityType.HOGLIN; case "minecraft:bastion/mobs/hoglin" -> EntityType.HOGLIN;
case "minecraft:bastion/mobs/piglin_melee" -> EntityType.PIGLIN_BRUTE; case "minecraft:bastion/mobs/piglin_melee" -> EntityType.PIGLIN_BRUTE;
case "minecraft:village/common/cats" -> EntityType.CAT; case "minecraft:village/common/cats" -> EntityType.CAT;
case "minecraft:village/common/horses" -> EntityType.HORSE; case "minecraft:village/common/horses" -> EntityType.HORSE;
case "minecraft:village/common/sheep" -> EntityType.SHEEP; case "minecraft:village/common/sheep" -> EntityType.SHEEP;
case "minecraft:village/common/pigs" -> EntityType.PIG; case "minecraft:village/common/pigs" -> EntityType.PIG;
case "minecraft:village/common/cows" -> EntityType.COW; case "minecraft:village/common/cows" -> EntityType.COW;
case "minecraft:village/common/iron_golem" -> EntityType.IRON_GOLEM; case "minecraft:village/common/iron_golem" -> EntityType.IRON_GOLEM;
case "minecraft:village/common/butcher_animals", "minecraft:village/common/animals" -> BUTCHER_ANIMALS.get(rand.nextInt(3)); case "minecraft:village/common/butcher_animals", "minecraft:village/common/animals" ->
default -> null; BUTCHER_ANIMALS.get(rand.nextInt(3));
}; default -> null;
// Boxed };
if (type == null && bjb.getPool().startsWith("minecraft:boxed/")) { // Boxed
String entString = bjb.getPool().toUpperCase(Locale.ENGLISH).substring(16, bjb.getPool().length()); if (type == null && bjb.getPool().startsWith("minecraft:boxed/")) {
type = Enums.getIfPresent(EntityType.class, entString).orNull(); String entString = bjb.getPool().toUpperCase(Locale.ENGLISH).substring(16, bjb.getPool().length());
} type = Enums.getIfPresent(EntityType.class, entString).orNull();
// Villagers }
if (bjb.getPool().contains("zombie/villagers")) { // Villagers
type = EntityType.ZOMBIE_VILLAGER; if (bjb.getPool().contains("zombie/villagers")) {
} else if (bjb.getPool().contains("villagers")) { type = EntityType.ZOMBIE_VILLAGER;
type = EntityType.VILLAGER; } else if (bjb.getPool().contains("villagers")) {
} type = EntityType.VILLAGER;
//if (type == null) { }
// BentoBox.getInstance().logDebug(bjb.getPool()); // if (type == null) {
//} // BentoBox.getInstance().logDebug(bjb.getPool());
// Spawn it // }
if (type != null) { // Spawn it
Entity e = b.getWorld().spawnEntity(b.getRelative(BlockFace.UP).getLocation(), type); if (type != null) {
if (e != null) { Entity e = b.getWorld().spawnEntity(b.getRelative(BlockFace.UP).getLocation(), type);
e.setPersistent(true); if (e != null) {
} e.setPersistent(true);
//BentoBox.getInstance().logDebug("Spawned a " + type + " at " + b.getRelative(BlockFace.UP).getLocation()); }
} // BentoBox.getInstance().logDebug("Spawned a " + type + " at " +
// b.getRelative(BlockFace.UP).getLocation());
}
} }
/** /**
* Corrects the direction of a block based on the structure's rotation * Corrects the direction of a block based on the structure's rotation
* @param finalState - the final block state of the block, which may include a facing: direction *
* @param sr - the structure's rotation * @param finalState - the final block state of the block, which may include a
* facing: direction
* @param sr - the structure's rotation
* @return a rewritten blockstate with the updated direction, if required * @return a rewritten blockstate with the updated direction, if required
*/ */
private static String correctDirection(String finalState, StructureRotation sr) { private static String correctDirection(String finalState, StructureRotation sr) {
if (sr.equals(StructureRotation.NONE)) { if (sr.equals(StructureRotation.NONE)) {
// No change // No change
return finalState; return finalState;
} }
BlockFace oldDirection = getDirection(finalState); BlockFace oldDirection = getDirection(finalState);
BlockFace newDirection = getNewDirection(oldDirection, sr); BlockFace newDirection = getNewDirection(oldDirection, sr);
if (newDirection.equals(BlockFace.SELF)) { if (newDirection.equals(BlockFace.SELF)) {
// No change - shouldn't happen, but just in case // No change - shouldn't happen, but just in case
return finalState; return finalState;
} }
return finalState.replace(oldDirection.name().toLowerCase(Locale.ENGLISH), newDirection.name().toLowerCase(Locale.ENGLISH)); return finalState.replace(oldDirection.name().toLowerCase(Locale.ENGLISH),
newDirection.name().toLowerCase(Locale.ENGLISH));
} }
/** /**
* Adjusts the direction based on the StructureRotation * Adjusts the direction based on the StructureRotation
*
* @param oldDirection the old direction to adjust * @param oldDirection the old direction to adjust
* @param sr the structure rotation * @param sr the structure rotation
* @return the new direction, or SELF if something weird happens * @return the new direction, or SELF if something weird happens
*/ */
private static BlockFace getNewDirection(BlockFace oldDirection, StructureRotation sr) { private static BlockFace getNewDirection(BlockFace oldDirection, StructureRotation sr) {
if (sr.equals(StructureRotation.CLOCKWISE_180)) { if (sr.equals(StructureRotation.CLOCKWISE_180)) {
return oldDirection.getOppositeFace(); return oldDirection.getOppositeFace();
} else if (sr.equals(StructureRotation.CLOCKWISE_90)) { } else if (sr.equals(StructureRotation.CLOCKWISE_90)) {
return switch(oldDirection) { return switch (oldDirection) {
case EAST -> BlockFace.SOUTH; case EAST -> BlockFace.SOUTH;
case NORTH -> BlockFace.EAST; case NORTH -> BlockFace.EAST;
case SOUTH -> BlockFace.WEST; case SOUTH -> BlockFace.WEST;
case WEST -> BlockFace.NORTH; case WEST -> BlockFace.NORTH;
default -> BlockFace.SELF; default -> BlockFace.SELF;
}; };
} else if (sr.equals(StructureRotation.COUNTERCLOCKWISE_90)) { } else if (sr.equals(StructureRotation.COUNTERCLOCKWISE_90)) {
return switch(oldDirection) { return switch (oldDirection) {
case EAST -> BlockFace.NORTH; case EAST -> BlockFace.NORTH;
case NORTH -> BlockFace.WEST; case NORTH -> BlockFace.WEST;
case SOUTH -> BlockFace.EAST; case SOUTH -> BlockFace.EAST;
case WEST -> BlockFace.SOUTH; case WEST -> BlockFace.SOUTH;
default -> BlockFace.SELF; default -> BlockFace.SELF;
}; };
} }
return BlockFace.SELF; return BlockFace.SELF;
} }
/** /**
* Looks for north, south, east, west in the blockstate. * Looks for north, south, east, west in the blockstate.
*
* @param finalState - the final block state of the block * @param finalState - the final block state of the block
* @return direction, if found, otherwise SELF * @return direction, if found, otherwise SELF
*/ */
private static BlockFace getDirection(String finalState) { private static BlockFace getDirection(String finalState) {
return CARDINALS.stream().filter(bf -> finalState.contains(bf.name().toLowerCase(Locale.ENGLISH))).findFirst().orElse(BlockFace.SELF); return CARDINALS.stream().filter(bf -> finalState.contains(bf.name().toLowerCase(Locale.ENGLISH))).findFirst()
.orElse(BlockFace.SELF);
} }
private static String nmsData(Block block) { private static String nmsData(Block block) {
Location w = block.getLocation(); Location w = block.getLocation();
CraftWorld cw = (CraftWorld) w.getWorld(); // CraftWorld is NMS one CraftWorld cw = (CraftWorld) w.getWorld(); // CraftWorld is NMS one
// for 1.13+ (we have use WorldServer) // for 1.13+ (we have use WorldServer)
TileEntity te = cw.getHandle().c_(new BlockPosition(w.getBlockX(), w.getBlockY(), w.getBlockZ())); TileEntity te = cw.getHandle().c_(new BlockPosition(w.getBlockX(), w.getBlockY(), w.getBlockZ()));
try { try {
PacketPlayOutTileEntityData packet = ((PacketPlayOutTileEntityData) te.h()); // get update packet from NMS object PacketPlayOutTileEntityData packet = ((PacketPlayOutTileEntityData) te.h()); // get update packet from NMS
// here we should use reflection because "c" field isn't accessible // object
Field f = packet.getClass().getDeclaredField("c"); // get field // here we should use reflection because "c" field isn't accessible
f.setAccessible(true); // make it available Field f = packet.getClass().getDeclaredField("c"); // get field
NBTTagCompound nbtTag = (NBTTagCompound) f.get(packet); f.setAccessible(true); // make it available
return nbtTag.toString(); // this will show what you want NBTTagCompound nbtTag = (NBTTagCompound) f.get(packet);
} catch (Exception exc) { return nbtTag.toString(); // this will show what you want
exc.printStackTrace(); } catch (Exception exc) {
} exc.printStackTrace();
return "Nothing"; }
return "Nothing";
} }
} }

View File

@ -15,6 +15,9 @@ boxed:
# Sub-command of main player command that will be run on each player command call. # Sub-command of main player command that will be run on each player command call.
# By default, it is sub-command 'go'. # By default, it is sub-command 'go'.
default-action: go default-action: go
# Ignore advancements
# If this is true, advancements will not change the size of the box.
ignore-advancements: false
# Announce advancements. We recommend you set the game rule `/gamerule announceAdvancements false` # Announce advancements. We recommend you set the game rule `/gamerule announceAdvancements false`
# but that blocks all new advancement announcements. This setting tells Boxed to broadcast new advancements. # but that blocks all new advancement announcements. This setting tells Boxed to broadcast new advancements.
broadcast-advancements: false broadcast-advancements: false