From 5910aa26855c8222853e2f1c22d0c876c4babcbb Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 19 Feb 2022 11:19:33 -0800 Subject: [PATCH 01/74] Version 1.20.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 981054aa8..329505f39 100644 --- a/pom.xml +++ b/pom.xml @@ -83,7 +83,7 @@ -LOCAL - 1.20.0 + 1.20.1 bentobox-world https://sonarcloud.io From 6c7d77f093fa4f378eaa8de5d44ac2c9d86fc3b3 Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 24 Feb 2022 08:02:20 -0800 Subject: [PATCH 02/74] Added name of the addon causing the issue. https://github.com/BentoBoxWorld/BentoBox/issues/1944 --- .../java/world/bentobox/bentobox/managers/LocalesManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java b/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java index 934148c78..46f67d3bd 100644 --- a/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/LocalesManager.java @@ -179,7 +179,7 @@ public class LocalesManager { } catch (InvalidConfigurationException e) { plugin.logError("Could not update locale file '" + lf + "' due to it being malformed: " + e.getMessage()); } catch (Exception e) { - plugin.logError("Error updating locale file '" + lf + "': " + e.getMessage()); + plugin.logError("Error updating locale file for " + addon.getDescription().getName() + "'s '" + lf + "': " + e.getMessage()); plugin.logStacktrace(e); } } From 6a009946f5487b4772e57fd500896c620997baed Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 26 Feb 2022 10:22:41 -0800 Subject: [PATCH 03/74] Use world min-height for island bounding box. --- .../java/world/bentobox/bentobox/database/objects/Island.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index 76e97a97e..4b463fe33 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -698,7 +698,7 @@ public class Island implements DataObject, MetaDataAble { * @since 1.5.2 */ public BoundingBox getBoundingBox() { - return new BoundingBox(getMinX(), 0.0D, getMinZ(), getMaxX(), world.getMaxHeight(), getMaxZ()); + return new BoundingBox(getMinX(), world.getMinHeight(), getMinZ(), getMaxX(), world.getMaxHeight(), getMaxZ()); } /** From 50b677f4c53bfc0c11c9904331b4e37536bc8d8c Mon Sep 17 00:00:00 2001 From: BONNe Date: Sat, 12 Mar 2022 12:15:10 +0200 Subject: [PATCH 04/74] Fixes a bug when fallback could not use reusable There was an issue in PanelItemTemplate that prevented fallback buttons to be "reusable" things. The issue was that reusable items were not passed to the panel item reader. --- .../bentobox/bentobox/api/panels/reader/TemplateReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java index d98d501ad..a85e0d733 100644 --- a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java +++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java @@ -219,7 +219,7 @@ public class TemplateReader // If it contains a section, then build a new button template from it. template.addButtonTemplate(rowIndex, columnIndex, - readPanelItemTemplate(line.getConfigurationSection(String.valueOf(columnIndex + 1)))); + readPanelItemTemplate(line.getConfigurationSection(String.valueOf(columnIndex + 1)), null, panelItemDataMap)); } else if (line.isString(String.valueOf(columnIndex + 1))) { From 945bfa66ee92a32c49538269c4808c5d5c334683 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 12 Mar 2022 12:20:27 -0800 Subject: [PATCH 05/74] Adjusted test to try to avoid errors --- .../flags/protection/EntityInteractListenerTest.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java index 7cde56a00..5e4c580ba 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java @@ -228,8 +228,8 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { */ @Test public void testOnPlayerInteractEntityNamingVillagerAllowedTradingNoNaming() { - when(island.isAllowed(any(), eq(Flags.TRADING))).thenReturn(true); - when(island.isAllowed(any(), eq(Flags.NAME_TAG))).thenReturn(false); + when(island.isAllowed(any(User.class), eq(Flags.TRADING))).thenReturn(true); + when(island.isAllowed(any(User.class), eq(Flags.NAME_TAG))).thenReturn(false); clickedEntity = mock(Villager.class); when(clickedEntity.getLocation()).thenReturn(location); PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(player, clickedEntity, hand); @@ -257,7 +257,7 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { */ @Test public void testOnPlayerInteractAtEntityWanderingTraderAllowed() { - when(island.isAllowed(any(), any())).thenReturn(true); + when(island.isAllowed(any(User.class), any())).thenReturn(true); clickedEntity = mock(WanderingTrader.class); when(clickedEntity.getType()).thenReturn(EntityType.WANDERING_TRADER); when(clickedEntity.getLocation()).thenReturn(location); @@ -272,8 +272,10 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { */ @Test public void testOnPlayerInteractEntityNamingWanderingTraderAllowedNoTrading() { - when(island.isAllowed(any(User.class), eq(Flags.TRADING))).thenReturn(false); - when(island.isAllowed(any(User.class), eq(Flags.NAME_TAG))).thenReturn(true); + when(island.isAllowed(any(User.class), + eq(Flags.TRADING))).thenReturn(false); + when(island.isAllowed(any(User.class), + eq(Flags.NAME_TAG))).thenReturn(true); clickedEntity = mock(WanderingTrader.class); when(clickedEntity.getType()).thenReturn(EntityType.WANDERING_TRADER); when(clickedEntity.getLocation()).thenReturn(location); From 546cf2c9b94006fc06b4c9780da3dae296f29545 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 12 Mar 2022 19:12:13 -0800 Subject: [PATCH 06/74] Fix for random test failures. --- .../listeners/flags/AbstractCommonSetup.java | 5 +++- .../EntityInteractListenerTest.java | 23 ++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java b/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java index ba9131e91..3140a989a 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java @@ -21,10 +21,12 @@ import org.bukkit.metadata.MetadataValue; import org.bukkit.plugin.PluginManager; import org.eclipse.jdt.annotation.Nullable; import org.junit.After; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; +import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; import com.google.common.collect.ImmutableSet; @@ -57,6 +59,7 @@ import world.bentobox.bentobox.util.Util; * @author tastybento * */ +@RunWith(PowerMockRunner.class) public abstract class AbstractCommonSetup { protected UUID uuid = UUID.randomUUID(); @@ -136,7 +139,7 @@ public abstract class AbstractCommonSetup { // Island - nothing is allowed by default when(island.isAllowed(any())).thenReturn(false); - when(island.isAllowed(any(), any())).thenReturn(false); + when(island.isAllowed(any(User.class), any())).thenReturn(false); when(island.getOwner()).thenReturn(uuid); when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid)); diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java index 5e4c580ba..0f258b06e 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java @@ -69,6 +69,10 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { position = new Vector(10,10,10); when(inv.getItemInMainHand()).thenReturn(new ItemStack(Material.NAME_TAG)); + // Initialize the Flags class. This is a workaround to prevent weird errors when mocking + // I think it's because the flag class needs to be initialized before use in argument matchers + Flags.TRADING.setDefaultSetting(false); + // Class under test eil = new EntityInteractListener(); } @@ -91,7 +95,7 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { */ @Test public void testOnPlayerInteractAtEntityArmorStandAllowed() { - when(island.isAllowed(any(), any())).thenReturn(true); + when(island.isAllowed(any(User.class), any())).thenReturn(true); clickedEntity = mock(ArmorStand.class); when(clickedEntity.getLocation()).thenReturn(location); PlayerInteractAtEntityEvent e = new PlayerInteractAtEntityEvent(player, clickedEntity, position, hand); @@ -118,7 +122,7 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { */ @Test public void testOnPlayerInteractEntityHorseAllowed() { - when(island.isAllowed(any(), any())).thenReturn(true); + when(island.isAllowed(any(User.class), any())).thenReturn(true); clickedEntity = mock(Horse.class); when(clickedEntity.getLocation()).thenReturn(location); PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(player, clickedEntity, hand); @@ -145,7 +149,7 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { */ @Test public void testOnPlayerInteractEntityMinecartAllowed() { - when(island.isAllowed(any(), any())).thenReturn(true); + when(island.isAllowed(any(User.class), any())).thenReturn(true); clickedEntity = mock(RideableMinecart.class); when(clickedEntity.getLocation()).thenReturn(location); PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(player, clickedEntity, hand); @@ -172,7 +176,7 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { */ @Test public void testOnPlayerInteractEntityBoatAllowed() { - when(island.isAllowed(any(), any())).thenReturn(true); + when(island.isAllowed(any(User.class), any())).thenReturn(true); clickedEntity = mock(Boat.class); when(clickedEntity.getLocation()).thenReturn(location); PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(player, clickedEntity, hand); @@ -199,7 +203,7 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { */ @Test public void testOnPlayerInteractAtEntityVillagerAllowed() { - when(island.isAllowed(any(), any())).thenReturn(true); + when(island.isAllowed(any(User.class), any())).thenReturn(true); clickedEntity = mock(Villager.class); when(clickedEntity.getLocation()).thenReturn(location); PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(player, clickedEntity, hand); @@ -212,9 +216,10 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { * Test method for {@link world.bentobox.bentobox.listeners.flags.protection.EntityInteractListener#onPlayerInteractEntity(org.bukkit.event.player.PlayerInteractAtEntityEvent)}. */ @Test + public void testOnPlayerInteractEntityNamingVillagerAllowedNoTrading() { - when(island.isAllowed(any(), eq(Flags.TRADING))).thenReturn(false); - when(island.isAllowed(any(), eq(Flags.NAME_TAG))).thenReturn(true); + when(island.isAllowed(any(User.class), eq(Flags.TRADING))).thenReturn(false); + when(island.isAllowed(any(User.class), eq(Flags.NAME_TAG))).thenReturn(true); clickedEntity = mock(Villager.class); when(clickedEntity.getLocation()).thenReturn(location); PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(player, clickedEntity, hand); @@ -271,8 +276,9 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { * Test method for {@link world.bentobox.bentobox.listeners.flags.protection.EntityInteractListener#onPlayerInteractEntity(org.bukkit.event.player.PlayerInteractAtEntityEvent)}. */ @Test + public void testOnPlayerInteractEntityNamingWanderingTraderAllowedNoTrading() { - when(island.isAllowed(any(User.class), + when(island.isAllowed(any(), eq(Flags.TRADING))).thenReturn(false); when(island.isAllowed(any(User.class), eq(Flags.NAME_TAG))).thenReturn(true); @@ -289,6 +295,7 @@ public class EntityInteractListenerTest extends AbstractCommonSetup { * Test method for {@link world.bentobox.bentobox.listeners.flags.protection.EntityInteractListener#onPlayerInteractEntity(org.bukkit.event.player.PlayerInteractAtEntityEvent)}. */ @Test + public void testOnPlayerInteractEntityNamingWanderingTraderAllowedTradingNoNaming() { when(island.isAllowed(any(User.class), eq(Flags.TRADING))).thenReturn(true); when(island.isAllowed(any(User.class), eq(Flags.NAME_TAG))).thenReturn(false); From e7599ec8053ca60bcada0aca80da80dd89486fc6 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 12 Mar 2022 19:20:53 -0800 Subject: [PATCH 07/74] Added 1.18.2 support --- .../world/bentobox/bentobox/versions/ServerCompatibility.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java index cc2ff5bc8..6fd5d1011 100644 --- a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java +++ b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java @@ -194,6 +194,10 @@ public class ServerCompatibility { * @since 1.19.0 */ V1_18_1(Compatibility.COMPATIBLE), + /** + * @since 1.20.1 + */ + V1_18_2(Compatibility.COMPATIBLE), ; private final Compatibility compatibility; From 0cf1d43a2938107af0fcf5a7f7ffd5cdaf71d91d Mon Sep 17 00:00:00 2001 From: BONNe Date: Wed, 16 Mar 2022 09:51:22 +0200 Subject: [PATCH 08/74] Address unnecessary PVP reports on each teleport (#1948) If a player is teleporting on the same island in the same dimension, it keeps spamming that PVP is enabled in dimension. It should be enough with sending messages when the player teleports to the island. Fixes #1885 --- .../listeners/flags/settings/PVPListener.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java index 95cf5ee01..c0c46b964 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/settings/PVPListener.java @@ -30,6 +30,7 @@ import world.bentobox.bentobox.api.events.flags.FlagSettingChangeEvent; import world.bentobox.bentobox.api.flags.Flag; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.lists.Flags; import world.bentobox.bentobox.managers.RanksManager; @@ -239,10 +240,20 @@ public class PVPListener extends FlagListener { if (e.getTo() == null) { return; } + + // Get previous island to skip reporting if island is not changed. + Island previousIsland = this.getIslands().getIslandAt(e.getFrom()).orElse(null); + getIslands().getIslandAt(e.getTo()).ifPresent(island -> { if (island.getMemberSet(RanksManager.COOP_RANK).contains(e.getPlayer().getUniqueId())) { return; } + + if (e.getFrom().getWorld() == e.getTo().getWorld() && island == previousIsland) { + // do not report as it is the same world and same island. + return; + } + if (island.isAllowed(Flags.PVP_OVERWORLD)) { alertUser(e.getPlayer(), Flags.PVP_OVERWORLD); } From 6d59e79e784fb9e44d3ba07021354b49aaac15be Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 18 Mar 2022 15:06:31 +0200 Subject: [PATCH 09/74] Fixes bug with Safe Spot Teleport (#1951) There was a bug that prevented finding a safe spot if all valid blocks were in height with the `startY` location. Reported via discord. --- .../world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java b/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java index a413010e5..370d1cf4a 100644 --- a/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java +++ b/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java @@ -226,7 +226,7 @@ public class SafeSpotTeleport { // Check the safe spot at the current height for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { - if (minY >= startY && checkBlock(chunk, x, startY, z)) { + if (minY <= startY && checkBlock(chunk, x, startY, z)) { return true; } maxY = Math.max(chunk.getHighestBlockYAt(x, z), maxY); From fd44e03b7b01daca540131611cf7a861a14a31b7 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 18 Mar 2022 13:59:29 +0000 Subject: [PATCH 10/74] Fix Exception error reported by IDE I am not sure why Eclipse is saying this is an error. --- src/main/java/world/bentobox/bentobox/util/IslandInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/util/IslandInfo.java b/src/main/java/world/bentobox/bentobox/util/IslandInfo.java index 5e3e76414..8ba3649b6 100644 --- a/src/main/java/world/bentobox/bentobox/util/IslandInfo.java +++ b/src/main/java/world/bentobox/bentobox/util/IslandInfo.java @@ -57,7 +57,7 @@ public class IslandInfo { try { String dateTimeFormat = plugin.getLocalesManager().get("commands.admin.info.last-login-date-time-format"); formattedDate = new SimpleDateFormat(dateTimeFormat).format(new Date(lastPlayed)); - } catch (NullPointerException | IllegalArgumentException ignored) { + } catch (Exception ignored) { formattedDate = new Date(lastPlayed).toString(); } user.sendMessage("commands.admin.info.last-login","[date]", formattedDate); From 4e8ca6d22c99064424ae8c76005a3927415c16c9 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 18 Mar 2022 13:59:41 +0000 Subject: [PATCH 11/74] Fix for kicking offline players https://github.com/BentoBoxWorld/BentoBox/issues/1950 --- .../world/bentobox/bentobox/managers/PlayersManager.java | 6 +++--- .../bentobox/bentobox/managers/PlayersManagerTest.java | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java index fc1c7e44c..1ab0b7d86 100644 --- a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java @@ -577,17 +577,17 @@ public class PlayersManager { plugin.getVault().ifPresent(vault -> vault.withdraw(target, vault.getBalance(target), world)); } // Reset the health - if (plugin.getIWM().isOnLeaveResetHealth(world)) { + if (plugin.getIWM().isOnLeaveResetHealth(world) && target.isPlayer()) { Util.resetHealth(target.getPlayer()); } // Reset the hunger - if (plugin.getIWM().isOnLeaveResetHunger(world)) { + if (plugin.getIWM().isOnLeaveResetHunger(world) && target.isPlayer()) { target.getPlayer().setFoodLevel(20); } // Reset the XP - if (plugin.getIWM().isOnLeaveResetXP(world)) { + if (plugin.getIWM().isOnLeaveResetXP(world) && target.isPlayer()) { target.getPlayer().setTotalExperience(0); } // Save player diff --git a/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java index c99d1b0da..033e154ac 100644 --- a/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java @@ -169,6 +169,7 @@ public class PlayersManagerTest { when(user.getPlayer()).thenReturn(p); when(user.getName()).thenReturn("tastybento"); when(user.isOnline()).thenReturn(true); + when(user.isPlayer()).thenReturn(true); User.setPlugin(plugin); From 3ba6620e735fe47b170326e17ab168a2a31f2867 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sat, 19 Mar 2022 16:22:03 +0200 Subject: [PATCH 12/74] Add an option in SafeSpotTeleport to cancel if fail (#1952) There was no option to cancel teleportation if SafeSpotTeleport could not find a valid spot. This option could be used to avoid creating "backup" blocks in situations when teleportation is avoidable, f.e. visiting an island. --- .../util/teleport/SafeSpotTeleport.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java b/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java index 370d1cf4a..3e427a0a2 100644 --- a/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java +++ b/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java @@ -51,6 +51,7 @@ public class SafeSpotTeleport { private final AtomicBoolean checking = new AtomicBoolean(); private BukkitTask task; private boolean portal; + private boolean cancelIfFail; // Locations private Location bestSpot; private Iterator> chunksToScanIterator; @@ -73,6 +74,7 @@ public class SafeSpotTeleport { this.result = builder.getResult(); this.world = Objects.requireNonNull(location.getWorld()); this.maxHeight = world.getMaxHeight() - 20; + this.cancelIfFail = builder.isCancelIfFail(); // Try to go Util.getChunkAtAsync(location).thenRun(() -> tryToGo(builder.getFailureMessage())); } @@ -150,7 +152,7 @@ public class SafeSpotTeleport { if (!plugin.getIWM().inWorld(entity.getLocation())) { // Last resort player.performCommand("spawn"); - } else { + } else if (!this.cancelIfFail) { // Create a spot for the player to be if (world.getEnvironment().equals(Environment.NETHER)) { makeAndTeleport(Material.NETHERRACK); @@ -336,6 +338,7 @@ public class SafeSpotTeleport { private Location location; private Runnable runnable; private Runnable failRunnable; + private boolean cancelIfFail; public Builder(BentoBox plugin) { this.plugin = plugin; @@ -461,6 +464,20 @@ public class SafeSpotTeleport { return new SafeSpotTeleport(this); } + + /** + * This method allows stopping "safe" block generation if teleportation fails. + * @param cancelIfFail - value for canceling + * @return Builder + * @since 1.20.1 + */ + public Builder cancelIfFail(boolean cancelIfFail) + { + this.cancelIfFail = cancelIfFail; + return this; + } + + /** * The task to run after the player is safely teleported. * @@ -557,5 +574,13 @@ public class SafeSpotTeleport { } + /** + * @return the cancelIfFail + * @since 1.20.1 + */ + public boolean isCancelIfFail() + { + return this.cancelIfFail; + } } } From bda56763a85ccd628396abd4448e093b273bd101 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 19 Mar 2022 14:29:54 +0000 Subject: [PATCH 13/74] Replace peplaceAll with replace It does the same thing if the first argument is not a regex. --- .../bentobox/bentobox/database/yaml/YamlDatabaseHandler.java | 2 +- src/main/java/world/bentobox/bentobox/managers/WebManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java index 05b42d537..9db21cd98 100644 --- a/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java @@ -452,7 +452,7 @@ public class YamlDatabaseHandler extends AbstractDatabaseHandler { for (Entry object : value.entrySet()) { // Serialize all key and values String key = (String)serialize(object.getKey()); - key = key.replaceAll("\\.", ":dot:"); + key = key.replace("\\.", ":dot:"); result.put(key, serialize(object.getValue())); } // Save the list in the config file diff --git a/src/main/java/world/bentobox/bentobox/managers/WebManager.java b/src/main/java/world/bentobox/bentobox/managers/WebManager.java index dbd5ef1d5..a1c103253 100644 --- a/src/main/java/world/bentobox/bentobox/managers/WebManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/WebManager.java @@ -196,7 +196,7 @@ public class WebManager { @NonNull private String getContent(@NonNull GitHubRepository repo, String fileName) { try { - String content = repo.getContent(fileName).getContent().replaceAll("\\n", ""); + String content = repo.getContent(fileName).getContent().replace("\\n", ""); return new String(Base64.getDecoder().decode(content), StandardCharsets.UTF_8); } catch (IllegalAccessException e) { // Fail silently From 0b3ef8df6df41fa5fe79bd356083ccc0cb290fe5 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 19 Mar 2022 14:43:52 +0000 Subject: [PATCH 14/74] Use constants for common strings --- .../team/IslandTeamInviteAcceptCommand.java | 9 ++++--- .../api/panels/reader/TemplateReader.java | 26 +++++++++++-------- .../database/yaml/YamlDatabaseHandler.java | 2 +- .../bentobox/bentobox/hooks/VaultHook.java | 7 ++--- .../bentobox/bentobox/util/IslandInfo.java | 24 +++++++++-------- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java index ed543bce2..fa2615156 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java @@ -20,7 +20,8 @@ import world.bentobox.bentobox.util.Util; */ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand { - private final IslandTeamCommand itc; + private static final String INVALID_INVITE = "commands.island.team.invite.errors.invalid-invite"; + private final IslandTeamCommand itc; private UUID playerUUID; private UUID prospectiveOwnerUUID; @@ -47,7 +48,7 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand { // Get the island owner prospectiveOwnerUUID = itc.getInviter(playerUUID); if (prospectiveOwnerUUID == null) { - user.sendMessage("commands.island.team.invite.errors.invalid-invite"); + user.sendMessage(INVALID_INVITE); return false; } Invite invite = itc.getInvite(playerUUID); @@ -56,7 +57,7 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand { Island island = getIslands().getIsland(getWorld(), prospectiveOwnerUUID); String inviteUsage = getParent().getSubCommand("invite").map(CompositeCommand::getUsage).orElse(""); if (island == null || island.getRank(prospectiveOwnerUUID) < island.getRankCommand(inviteUsage)) { - user.sendMessage("commands.island.team.invite.errors.invalid-invite"); + user.sendMessage(INVALID_INVITE); itc.removeInvite(playerUUID); return false; } @@ -149,7 +150,7 @@ public class IslandTeamInviteAcceptCommand extends ConfirmableCommand { // Get the team's island Island teamIsland = getIslands().getIsland(getWorld(), prospectiveOwnerUUID); if (teamIsland == null) { - user.sendMessage("commands.island.team.invite.errors.invalid-invite"); + user.sendMessage(INVALID_INVITE); return; } if (teamIsland.getMemberSet(RanksManager.MEMBER_RANK, true).size() > getIslands().getMaxMembers(teamIsland, RanksManager.MEMBER_RANK)) { diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java index a85e0d733..c58a31bbc 100644 --- a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java +++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java @@ -41,6 +41,10 @@ public class TemplateReader private static final String BORDER = "border"; private static final String FORCE_SHOWN = "force-shown"; private static final String FALLBACK = "fallback"; + private static final String YML = ".yml"; + private static final String ACTIONS = "actions"; + private static final String TOOLTIP = "tooltip"; + private static final String CLICK_TYPE = "click-type"; /** @@ -73,7 +77,7 @@ public class TemplateReader return null; } - File file = new File(panelLocation, templateName.endsWith(".yml") ? templateName : templateName + ".yml"); + File file = new File(panelLocation, templateName.endsWith(YML) ? templateName : templateName + YML); if (!file.exists()) { @@ -337,9 +341,9 @@ public class TemplateReader } // Read Click data - if (section.isConfigurationSection("actions")) + if (section.isConfigurationSection(ACTIONS)) { - ConfigurationSection actionSection = section.getConfigurationSection("actions"); + ConfigurationSection actionSection = section.getConfigurationSection(ACTIONS); if (actionSection != null) { @@ -356,7 +360,7 @@ public class TemplateReader new ItemTemplateRecord.ActionRecords(clickType, actionDataSection.getString("type"), actionDataSection.getString("content"), - actionDataSection.getString("tooltip")); + actionDataSection.getString(TOOLTIP)); itemRecord.addAction(actionData); } } @@ -364,34 +368,34 @@ public class TemplateReader { ConfigurationSection actionDataSection = actionSection.getConfigurationSection(actionKey); - if (actionDataSection != null && actionDataSection.contains("click-type")) + if (actionDataSection != null && actionDataSection.contains(CLICK_TYPE)) { clickType = Enums.getIfPresent(ClickType.class, - actionDataSection.getString("click-type", "UNKNOWN").toUpperCase()). + actionDataSection.getString(CLICK_TYPE, "UNKNOWN").toUpperCase()). or(ClickType.UNKNOWN); ItemTemplateRecord.ActionRecords actionData = new ItemTemplateRecord.ActionRecords(clickType, actionKey, actionDataSection.getString("content"), - actionDataSection.getString("tooltip")); + actionDataSection.getString(TOOLTIP)); itemRecord.addAction(actionData); } } }); } } - else if (section.isList("actions")) + else if (section.isList(ACTIONS)) { // Read Click data as list which allows to have duplicate click types. - List> actionList = section.getMapList("actions"); + List> actionList = section.getMapList(ACTIONS); if (!actionList.isEmpty()) { actionList.forEach(valueMap -> { ClickType clickType = Enums.getIfPresent(ClickType.class, - String.valueOf(valueMap.get("click-type")).toUpperCase()).orNull(); + String.valueOf(valueMap.get(CLICK_TYPE)).toUpperCase()).orNull(); if (clickType != null) { @@ -399,7 +403,7 @@ public class TemplateReader new ItemTemplateRecord.ActionRecords(clickType, valueMap.containsKey("type") ? String.valueOf(valueMap.get("type")) : null, valueMap.containsKey("content") ? String.valueOf(valueMap.get("content")) : null, - valueMap.containsKey("tooltip") ? String.valueOf(valueMap.get("tooltip")) : null); + valueMap.containsKey(TOOLTIP) ? String.valueOf(valueMap.get(TOOLTIP)) : null); itemRecord.addAction(actionData); } }); diff --git a/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java index 9db21cd98..008ff23a9 100644 --- a/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/yaml/YamlDatabaseHandler.java @@ -289,7 +289,7 @@ public class YamlDatabaseHandler extends AbstractDatabaseHandler { // Convert any serialized dots back to dots // In YAML dots . cause a lot of problems, so I serialize them as :dot: // There may be a better way to do this. - key = key.replaceAll(":dot:", "."); + key = key.replace(":dot:", "."); Object mapKey = deserialize(key,Class.forName(keyType.getTypeName())); if (mapKey == null) { continue; diff --git a/src/main/java/world/bentobox/bentobox/hooks/VaultHook.java b/src/main/java/world/bentobox/bentobox/hooks/VaultHook.java index b4e6002c0..acece9e21 100644 --- a/src/main/java/world/bentobox/bentobox/hooks/VaultHook.java +++ b/src/main/java/world/bentobox/bentobox/hooks/VaultHook.java @@ -17,6 +17,7 @@ import world.bentobox.bentobox.api.user.User; public class VaultHook extends Hook { private static final String AMOUNT_MUST_BE_POSITIVE = "Amount must be positive."; + private static final String PLAYER_OR_OFFLINEPLAYER_REQUIRED = "User must be a Player or an OfflinePlayer"; private Economy economy; public VaultHook() { @@ -109,7 +110,7 @@ public class VaultHook extends Hook { */ public EconomyResponse withdraw(User user, double amount, World world) { if (!user.isOfflinePlayer()) { - throw new IllegalArgumentException("User must be a Player or an OfflinePlayer"); + throw new IllegalArgumentException(PLAYER_OR_OFFLINEPLAYER_REQUIRED); } if (amount < 0.0D) { throw new IllegalArgumentException(AMOUNT_MUST_BE_POSITIVE); @@ -149,7 +150,7 @@ public class VaultHook extends Hook { */ public EconomyResponse deposit(User user, double amount, World world) { if (!user.isOfflinePlayer()) { - throw new IllegalArgumentException("User must be a Player or an OfflinePlayer"); + throw new IllegalArgumentException(PLAYER_OR_OFFLINEPLAYER_REQUIRED); } if (amount < 0.0D) { throw new IllegalArgumentException(AMOUNT_MUST_BE_POSITIVE); @@ -198,7 +199,7 @@ public class VaultHook extends Hook { } if (!user.isOfflinePlayer()) { - throw new IllegalArgumentException("User must be a Player or an OfflinePlayer"); + throw new IllegalArgumentException(PLAYER_OR_OFFLINEPLAYER_REQUIRED); } if (world == null) diff --git a/src/main/java/world/bentobox/bentobox/util/IslandInfo.java b/src/main/java/world/bentobox/bentobox/util/IslandInfo.java index 8ba3649b6..e772b6ba5 100644 --- a/src/main/java/world/bentobox/bentobox/util/IslandInfo.java +++ b/src/main/java/world/bentobox/bentobox/util/IslandInfo.java @@ -21,7 +21,9 @@ import world.bentobox.bentobox.managers.RanksManager; */ public class IslandInfo { - private final BentoBox plugin; + private static final String XZ1 = "[xz1]"; + private static final String RANGE = "[range]"; + private final BentoBox plugin; private final Island island; private final @Nullable UUID owner; private final World world; @@ -62,18 +64,18 @@ public class IslandInfo { } user.sendMessage("commands.admin.info.last-login","[date]", formattedDate); - user.sendMessage("commands.admin.info.deaths", "[number]", String.valueOf(plugin.getPlayers().getDeaths(world, owner))); + user.sendMessage("commands.admin.info.deaths", TextVariables.NUMBER, String.valueOf(plugin.getPlayers().getDeaths(world, owner))); String resets = String.valueOf(plugin.getPlayers().getResets(world, owner)); String total = plugin.getIWM().getResetLimit(world) < 0 ? "Unlimited" : String.valueOf(plugin.getIWM().getResetLimit(world)); - user.sendMessage("commands.admin.info.resets-left", "[number]", resets, "[total]", total); + user.sendMessage("commands.admin.info.resets-left", TextVariables.NUMBER, resets, "[total]", total); // Show team members showMembers(user); } Vector location = island.getProtectionCenter().toVector(); user.sendMessage("commands.admin.info.island-protection-center", TextVariables.XYZ, Util.xyz(location)); user.sendMessage("commands.admin.info.island-center", TextVariables.XYZ, Util.xyz(island.getCenter().toVector())); - user.sendMessage("commands.admin.info.island-coords", "[xz1]", Util.xyz(new Vector(island.getMinX(), 0, island.getMinZ())), "[xz2]", Util.xyz(new Vector(island.getMaxX(), 0, island.getMaxZ()))); - user.sendMessage("commands.admin.info.protection-range", "[range]", String.valueOf(island.getProtectionRange())); + user.sendMessage("commands.admin.info.island-coords", XZ1, Util.xyz(new Vector(island.getMinX(), 0, island.getMinZ())), "[xz2]", Util.xyz(new Vector(island.getMaxX(), 0, island.getMaxZ()))); + user.sendMessage("commands.admin.info.protection-range", RANGE, String.valueOf(island.getProtectionRange())); if (!island.getBonusRanges().isEmpty()) { user.sendMessage("commands.admin.info.protection-range-bonus-title"); } @@ -84,8 +86,8 @@ public class IslandInfo { user.sendMessage(brb.getMessage(), TextVariables.NUMBER, String.valueOf(brb.getRange())); } }); - user.sendMessage("commands.admin.info.max-protection-range", "[range]", String.valueOf(island.getMaxEverProtectionRange())); - user.sendMessage("commands.admin.info.protection-coords", "[xz1]", Util.xyz(new Vector(island.getMinProtectedX(), 0, island.getMinProtectedZ())), "[xz2]", Util.xyz(new Vector(island.getMaxProtectedX() - 1, 0, island.getMaxProtectedZ() - 1))); + user.sendMessage("commands.admin.info.max-protection-range", RANGE, String.valueOf(island.getMaxEverProtectionRange())); + user.sendMessage("commands.admin.info.protection-coords", XZ1, Util.xyz(new Vector(island.getMinProtectedX(), 0, island.getMinProtectedZ())), "[xz2]", Util.xyz(new Vector(island.getMaxProtectedX() - 1, 0, island.getMaxProtectedZ() - 1))); if (island.isSpawn()) { user.sendMessage("commands.admin.info.is-spawn"); } @@ -110,17 +112,17 @@ public class IslandInfo { user.sendMessage("commands.admin.info.unowned"); } else { user.sendMessage("commands.admin.info.owner", "[owner]", plugin.getPlayers().getName(owner), TextVariables.UUID, owner.toString()); - user.sendMessage("commands.admin.info.deaths", "[number]", String.valueOf(plugin.getPlayers().getDeaths(world, owner))); + user.sendMessage("commands.admin.info.deaths", TextVariables.NUMBER, String.valueOf(plugin.getPlayers().getDeaths(world, owner))); String resets = String.valueOf(plugin.getPlayers().getResets(world, owner)); String total = plugin.getIWM().getResetLimit(world) < 0 ? "Unlimited" : String.valueOf(plugin.getIWM().getResetLimit(world)); - user.sendMessage("commands.admin.info.resets-left", "[number]", resets, "[total]", total); + user.sendMessage("commands.admin.info.resets-left", TextVariables.NUMBER, resets, "[total]", total); // Show team members showMembers(user); } Vector location = island.getProtectionCenter().toVector(); user.sendMessage("commands.admin.info.island-center", TextVariables.XYZ, Util.xyz(location)); - user.sendMessage("commands.admin.info.protection-range", "[range]", String.valueOf(island.getProtectionRange())); - user.sendMessage("commands.admin.info.protection-coords", "[xz1]", Util.xyz(new Vector(island.getMinProtectedX(), 0, island.getMinProtectedZ())), "[xz2]", Util.xyz(new Vector(island.getMaxProtectedX() - 1, 0, island.getMaxProtectedZ() - 1))); + user.sendMessage("commands.admin.info.protection-range", RANGE, String.valueOf(island.getProtectionRange())); + user.sendMessage("commands.admin.info.protection-coords", XZ1, Util.xyz(new Vector(island.getMinProtectedX(), 0, island.getMinProtectedZ())), "[xz2]", Util.xyz(new Vector(island.getMaxProtectedX() - 1, 0, island.getMaxProtectedZ() - 1))); if (island.isSpawn()) { user.sendMessage("commands.admin.info.is-spawn"); } From b0f141716668c85028c813661f7aa858f57e7a4d Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 19 Mar 2022 14:45:44 +0000 Subject: [PATCH 15/74] Use constants for common strings --- .../api/panels/reader/TemplateReader.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java index c58a31bbc..52931219a 100644 --- a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java +++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java @@ -45,6 +45,8 @@ public class TemplateReader private static final String ACTIONS = "actions"; private static final String TOOLTIP = "tooltip"; private static final String CLICK_TYPE = "click-type"; + private static final String CONTENT = "content"; + private static final String TYPE = "type"; /** @@ -129,7 +131,7 @@ public class TemplateReader String title = configurationSection.getString(TITLE); Panel.Type type = - Enums.getIfPresent(Panel.Type.class, configurationSection.getString("type", "INVENTORY")). + Enums.getIfPresent(Panel.Type.class, configurationSection.getString(TYPE, "INVENTORY")). or(Panel.Type.INVENTORY); PanelTemplateRecord.TemplateItem borderItem = null; @@ -198,7 +200,7 @@ public class TemplateReader PanelTemplateRecord template = new PanelTemplateRecord(type, title, borderItem, backgroundItem, forcedRows); // Read content - ConfigurationSection content = configurationSection.getConfigurationSection("content"); + ConfigurationSection content = configurationSection.getConfigurationSection(CONTENT); if (content == null) { @@ -358,8 +360,8 @@ public class TemplateReader { ItemTemplateRecord.ActionRecords actionData = new ItemTemplateRecord.ActionRecords(clickType, - actionDataSection.getString("type"), - actionDataSection.getString("content"), + actionDataSection.getString(TYPE), + actionDataSection.getString(CONTENT), actionDataSection.getString(TOOLTIP)); itemRecord.addAction(actionData); } @@ -377,7 +379,7 @@ public class TemplateReader ItemTemplateRecord.ActionRecords actionData = new ItemTemplateRecord.ActionRecords(clickType, actionKey, - actionDataSection.getString("content"), + actionDataSection.getString(CONTENT), actionDataSection.getString(TOOLTIP)); itemRecord.addAction(actionData); } @@ -401,8 +403,8 @@ public class TemplateReader { ItemTemplateRecord.ActionRecords actionData = new ItemTemplateRecord.ActionRecords(clickType, - valueMap.containsKey("type") ? String.valueOf(valueMap.get("type")) : null, - valueMap.containsKey("content") ? String.valueOf(valueMap.get("content")) : null, + valueMap.containsKey(TYPE) ? String.valueOf(valueMap.get(TYPE)) : null, + valueMap.containsKey(CONTENT) ? String.valueOf(valueMap.get(CONTENT)) : null, valueMap.containsKey(TOOLTIP) ? String.valueOf(valueMap.get(TOOLTIP)) : null); itemRecord.addAction(actionData); } From 51dbca0f99bc0c50c991dfb73fc0cae7a8041bbe Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 19 Mar 2022 15:12:04 +0000 Subject: [PATCH 16/74] Go back to replaceAll This is required. --- src/main/java/world/bentobox/bentobox/managers/WebManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/WebManager.java b/src/main/java/world/bentobox/bentobox/managers/WebManager.java index a1c103253..c04d1e9c5 100644 --- a/src/main/java/world/bentobox/bentobox/managers/WebManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/WebManager.java @@ -196,7 +196,7 @@ public class WebManager { @NonNull private String getContent(@NonNull GitHubRepository repo, String fileName) { try { - String content = repo.getContent(fileName).getContent().replace("\\n", ""); + String content = repo.getContent(fileName).getContent().replaceAll("\\n", ""); // replaceAll is required here return new String(Base64.getDecoder().decode(content), StandardCharsets.UTF_8); } catch (IllegalAccessException e) { // Fail silently From 6796fceee883f2bf5b4e769438a440f9d0267d35 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 19 Mar 2022 16:19:31 +0000 Subject: [PATCH 17/74] Clearer paster (#1953) * WIP - make easier to understand. * Small refactor of paster to make it easier to understand * Fix tabs to spaces. Sorry - new editor! * Fix tabs to spaces * Fix tab to spaces --- .../team/IslandTeamInviteAcceptCommand.java | 2 +- .../api/panels/reader/TemplateReader.java | 38 ++--- .../bentobox/blueprints/BlueprintPaster.java | 159 ++++++++++-------- 3 files changed, 105 insertions(+), 94 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java index fa2615156..bc5ceb271 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java @@ -21,7 +21,7 @@ import world.bentobox.bentobox.util.Util; public class IslandTeamInviteAcceptCommand extends ConfirmableCommand { private static final String INVALID_INVITE = "commands.island.team.invite.errors.invalid-invite"; - private final IslandTeamCommand itc; + private final IslandTeamCommand itc; private UUID playerUUID; private UUID prospectiveOwnerUUID; diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java index 52931219a..4407ae8d2 100644 --- a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java +++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java @@ -41,12 +41,12 @@ public class TemplateReader private static final String BORDER = "border"; private static final String FORCE_SHOWN = "force-shown"; private static final String FALLBACK = "fallback"; - private static final String YML = ".yml"; - private static final String ACTIONS = "actions"; - private static final String TOOLTIP = "tooltip"; - private static final String CLICK_TYPE = "click-type"; - private static final String CONTENT = "content"; - private static final String TYPE = "type"; + private static final String YML = ".yml"; + private static final String ACTIONS = "actions"; + private static final String TOOLTIP = "tooltip"; + private static final String CLICK_TYPE = "click-type"; + private static final String CONTENT = "content"; + private static final String TYPE = "type"; /** @@ -88,7 +88,7 @@ public class TemplateReader } final String panelKey = file.getAbsolutePath() + ":" + panelName; - + // Check if panel is already crafted. if (TemplateReader.loadedPanels.containsKey(panelKey)) { @@ -373,14 +373,14 @@ public class TemplateReader if (actionDataSection != null && actionDataSection.contains(CLICK_TYPE)) { clickType = Enums.getIfPresent(ClickType.class, - actionDataSection.getString(CLICK_TYPE, "UNKNOWN").toUpperCase()). - or(ClickType.UNKNOWN); - + actionDataSection.getString(CLICK_TYPE, "UNKNOWN").toUpperCase()). + or(ClickType.UNKNOWN); + ItemTemplateRecord.ActionRecords actionData = - new ItemTemplateRecord.ActionRecords(clickType, - actionKey, - actionDataSection.getString(CONTENT), - actionDataSection.getString(TOOLTIP)); + new ItemTemplateRecord.ActionRecords(clickType, + actionKey, + actionDataSection.getString(CONTENT), + actionDataSection.getString(TOOLTIP)); itemRecord.addAction(actionData); } } @@ -397,15 +397,15 @@ public class TemplateReader { actionList.forEach(valueMap -> { ClickType clickType = Enums.getIfPresent(ClickType.class, - String.valueOf(valueMap.get(CLICK_TYPE)).toUpperCase()).orNull(); + String.valueOf(valueMap.get(CLICK_TYPE)).toUpperCase()).orNull(); if (clickType != null) { ItemTemplateRecord.ActionRecords actionData = - new ItemTemplateRecord.ActionRecords(clickType, - valueMap.containsKey(TYPE) ? String.valueOf(valueMap.get(TYPE)) : null, - valueMap.containsKey(CONTENT) ? String.valueOf(valueMap.get(CONTENT)) : null, - valueMap.containsKey(TOOLTIP) ? String.valueOf(valueMap.get(TOOLTIP)) : null); + new ItemTemplateRecord.ActionRecords(clickType, + valueMap.containsKey(TYPE) ? String.valueOf(valueMap.get(TYPE)) : null, + valueMap.containsKey(CONTENT) ? String.valueOf(valueMap.get(CONTENT)) : null, + valueMap.containsKey(TOOLTIP) ? String.valueOf(valueMap.get(TOOLTIP)) : null); itemRecord.addAction(actionData); } }); diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java index 6528f58bd..c91cc14b0 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java @@ -134,99 +134,110 @@ public class BlueprintPaster { this.location = island.getProtectionCenter().toVector().subtract(off).toLocation(world); } + private record Bits(Map blocks, + Map attached, + Map> entities, + Iterator> it, + Iterator> it2, + Iterator>> it3, + int pasteSpeed) {} /** * The main pasting method */ public CompletableFuture paste() { CompletableFuture result = new CompletableFuture<>(); // Iterators for the various maps to paste - Map blocks = blueprint.getBlocks() == null ? Collections.emptyMap() : blueprint.getBlocks(); - Map attached = blueprint.getAttached() == null ? Collections.emptyMap() : blueprint.getAttached(); - Map> entities = blueprint.getEntities() == null ? Collections.emptyMap() : blueprint.getEntities(); - Iterator> it = blocks.entrySet().iterator(); - Iterator> it2 = attached.entrySet().iterator(); - Iterator>> it3 = entities.entrySet().iterator(); + final Map blocks = blueprint.getBlocks() == null ? Collections.emptyMap() : blueprint.getBlocks(); + final Map attached = blueprint.getAttached() == null ? Collections.emptyMap() : blueprint.getAttached(); + final Map> entities = blueprint.getEntities() == null ? Collections.emptyMap() : blueprint.getEntities(); // Initial state & speed pasteState = PasteState.CHUNK_LOAD; - final int pasteSpeed = plugin.getSettings().getPasteSpeed(); // If this is an island OVERWORLD paste, get the island owner. final Optional owner = Optional.ofNullable(island) .filter(i -> location.getWorld().getEnvironment().equals(World.Environment.NORMAL)) .map(i -> User.getInstance(i.getOwner())); // Tell the owner we're pasting blocks and how much time it might take - owner.ifPresent(user -> { - // Estimated time: - double total = (double) blocks.size() + attached.size() + entities.size(); - BigDecimal time = BigDecimal.valueOf(total / (pasteSpeed * 20.0D) + (chunkLoadTime / 1000.0D)).setScale(1, RoundingMode.UP); - user.sendMessage("commands.island.create.pasting.estimated-time", TextVariables.NUMBER, String.valueOf(time.doubleValue())); - // We're pasting blocks! - user.sendMessage("commands.island.create.pasting.blocks", TextVariables.NUMBER, String.valueOf(blocks.size() + attached.size())); - }); - - pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { - long timer = System.currentTimeMillis(); - int count = 0; - if (pasteState.equals(PasteState.CHUNK_LOAD)) { - pasteState = PasteState.CHUNK_LOADING; - // Load chunk - Util.getChunkAtAsync(location).thenRun(() -> { - pasteState = PasteState.BLOCKS; - long duration = System.currentTimeMillis() - timer; - if (duration > chunkLoadTime) { - chunkLoadTime = duration; - } - }); - } - while (pasteState.equals(PasteState.BLOCKS) && count < pasteSpeed && it.hasNext()) { - pasteBlock(location, it.next()); - count++; - } - while (pasteState.equals(PasteState.ATTACHMENTS) && count < pasteSpeed && it2.hasNext()) { - pasteBlock(location, it2.next()); - count++; - } - while (pasteState.equals(PasteState.ENTITIES) && count < pasteSpeed && it3.hasNext()) { - pasteEntity(location, it3.next()); - count++; - } - // STATE SHIFT - if (pasteState.equals(PasteState.BLOCKS) && !it.hasNext()) { - // Blocks done - // Next paste attachments - pasteState = PasteState.ATTACHMENTS; - } - else if (pasteState.equals(PasteState.ATTACHMENTS) && !it2.hasNext()) { - // Attachments done. Next paste entities - pasteState = PasteState.ENTITIES; - if (entities.size() != 0) { - owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.entities", TextVariables.NUMBER, String.valueOf(entities.size()))); - } - } - else if (pasteState.equals(PasteState.ENTITIES) && !it3.hasNext()) { - pasteState = PasteState.DONE; - owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.done")); - } - else if (pasteState.equals(PasteState.DONE)) { - // All done. Cancel task - // Set pos1 and 2 if this was a clipboard paste - if (island == null && clipboard != null) { - clipboard.setPos1(pos1); - clipboard.setPos2(pos2); - } - result.complete(true); - pasteState = PasteState.CANCEL; - } else if (pasteState.equals(PasteState.CANCEL)) { - // This state makes sure the follow-on task only ever runs once - pastingTask.cancel(); - result.complete(true); - } - }, 0L, 1L); + owner.ifPresent(user -> tellOwner(user, blocks.size(), attached.size(), entities.size(), plugin.getSettings().getPasteSpeed())); + Bits bits = new Bits(blocks, attached, entities, + blocks.entrySet().iterator(), attached.entrySet().iterator(), entities.entrySet().iterator(), + plugin.getSettings().getPasteSpeed()); + pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> pasterTask(result, owner, bits), 0L, 1L); return result; } + private void pasterTask(CompletableFuture result, Optional owner, Bits bits) { + final int pasteSpeed = plugin.getSettings().getPasteSpeed(); + + long timer = System.currentTimeMillis(); + int count = 0; + if (pasteState.equals(PasteState.CHUNK_LOAD)) { + pasteState = PasteState.CHUNK_LOADING; + // Load chunk + Util.getChunkAtAsync(location).thenRun(() -> { + pasteState = PasteState.BLOCKS; + long duration = System.currentTimeMillis() - timer; + if (duration > chunkLoadTime) { + chunkLoadTime = duration; + } + }); + } + while (pasteState.equals(PasteState.BLOCKS) && count < pasteSpeed && bits.it.hasNext()) { + pasteBlock(location, bits.it.next()); + count++; + } + while (pasteState.equals(PasteState.ATTACHMENTS) && count < pasteSpeed && bits.it2.hasNext()) { + pasteBlock(location, bits.it2.next()); + count++; + } + while (pasteState.equals(PasteState.ENTITIES) && count < pasteSpeed && bits.it3.hasNext()) { + pasteEntity(location, bits.it3.next()); + count++; + } + // STATE SHIFT + if (pasteState.equals(PasteState.BLOCKS) && !bits.it.hasNext()) { + // Blocks done + // Next paste attachments + pasteState = PasteState.ATTACHMENTS; + } + else if (pasteState.equals(PasteState.ATTACHMENTS) && !bits.it2.hasNext()) { + // Attachments done. Next paste entities + pasteState = PasteState.ENTITIES; + if (bits.entities.size() != 0) { + owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.entities", TextVariables.NUMBER, String.valueOf(bits.entities.size()))); + } + } + else if (pasteState.equals(PasteState.ENTITIES) && !bits.it3.hasNext()) { + pasteState = PasteState.DONE; + owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.done")); + } + else if (pasteState.equals(PasteState.DONE)) { + // All done. Cancel task + // Set pos1 and 2 if this was a clipboard paste + if (island == null && clipboard != null) { + clipboard.setPos1(pos1); + clipboard.setPos2(pos2); + } + pasteState = PasteState.CANCEL; + result.complete(true); + } else if (pasteState.equals(PasteState.CANCEL)) { + // This state makes sure the follow-on task only ever runs once + pastingTask.cancel(); + result.complete(true); + } + } + + private void tellOwner(User user, int blocksSize, int attachedSize, int entitiesSize, int pasteSpeed) { + // Estimated time: + double total = (double) blocksSize + attachedSize + entitiesSize; + BigDecimal time = BigDecimal.valueOf(total / (pasteSpeed * 20.0D) + (chunkLoadTime / 1000.0D)).setScale(1, RoundingMode.UP); + user.sendMessage("commands.island.create.pasting.estimated-time", TextVariables.NUMBER, String.valueOf(time.doubleValue())); + // We're pasting blocks! + user.sendMessage("commands.island.create.pasting.blocks", TextVariables.NUMBER, String.valueOf(blocksSize + attachedSize)); + } + private void pasteBlock(Location location, Entry entry) { World world = location.getWorld(); Location pasteTo = location.clone().add(entry.getKey()); From 9f21314818a55ab21d778930828f0d77f8e84ac1 Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 25 Mar 2022 23:29:14 +0200 Subject: [PATCH 18/74] Improve team kick command (#1957) The kick command has an unnecessary owner check. As command should be configurable by island owners, then limiting it to an owner is wrong. Add a code that allows kicking only lower-ranked players. Add message that shows who kicked from the island. Add message that shows that rank does not allow to kick. --- .../island/team/IslandTeamKickCommand.java | 16 +++++++++++----- src/main/resources/locales/en-US.yml | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java index 2aab070fa..e89bc3594 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java @@ -42,10 +42,6 @@ public class IslandTeamKickCommand extends ConfirmableCommand { user.sendMessage("general.errors.no-team"); return false; } - if (!user.getUniqueId().equals(getOwner(getWorld(), user))) { - user.sendMessage("general.errors.not-owner"); - return false; - } // Check rank to use command Island island = getIslands().getIsland(getWorld(), user); int rank = Objects.requireNonNull(island).getRank(user); @@ -72,6 +68,14 @@ public class IslandTeamKickCommand extends ConfirmableCommand { user.sendMessage("general.errors.not-in-team"); return false; } + + int targetRank = Objects.requireNonNull(island).getRank(user); + if (rank <= targetRank) { + user.sendMessage("commands.island.team.kick.cannot-kick-rank", + TextVariables.NAME, getPlayers().getName(targetUUID)); + return false; + } + if (!getSettings().isKickConfirmation()) { kick(user, targetUUID); return true; @@ -93,7 +97,9 @@ public class IslandTeamKickCommand extends ConfirmableCommand { if (event.isCancelled()) { return; } - target.sendMessage("commands.island.team.kick.owner-kicked", TextVariables.GAMEMODE, getAddon().getDescription().getName()); + target.sendMessage("commands.island.team.kick.player-kicked", + TextVariables.GAMEMODE, getAddon().getDescription().getName(), + TextVariables.NAME, user.getName()); getIslands().removePlayer(getWorld(), targetUUID); // Clean the target player diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 2da92e94e..51c28f0c9 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -652,8 +652,9 @@ commands: kick: description: "remove a member from your island" parameters: "" - owner-kicked: "&c The owner kicked you from the island in [gamemode]!" + player-kicked: "&c The [name] kicked you from the island in [gamemode]!" cannot-kick: "&c You cannot kick yourself!" + cannot-kick-rank: "&c Your rank does not allow to kick [name]!" success: "&b [name] &a has been kicked from your island." demote: description: "demote a player on your island down a rank" From 36751d5573ab2af32b0cfa4ff7d124a0cccd8f60 Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 1 Apr 2022 00:12:27 +0300 Subject: [PATCH 19/74] Solve crashes with Addon#allLoaded call (#1959) If some addon has code in Addon#allLoaded that crashes the call, then it did not disable addon as well as did not call allLoaded for every other addon that was left in the list. This should be solved by adding an extra try-catch. --- .../bentobox/managers/AddonsManager.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java b/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java index 0026d94c3..0d47ed93e 100644 --- a/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/AddonsManager.java @@ -666,7 +666,28 @@ public class AddonsManager { * @since 1.8.0 */ public void allLoaded() { - addons.forEach(Addon::allLoaded); + this.getEnabledAddons().forEach(this::allLoaded); } + + /** + * This method calls Addon#allLoaded in safe manner. If for some reason addon crashes on Addon#allLoaded, then + * it will disable itself without harming other addons. + * @param addon Addon that should trigger Addon#allLoaded method. + */ + private void allLoaded(@NonNull Addon addon) { + try { + addon.allLoaded(); + } catch (NoClassDefFoundError | NoSuchMethodError | NoSuchFieldError e) { + // Looks like the addon is incompatible, because it tries to refer to missing classes... + this.handleAddonIncompatibility(addon, e); + // Disable addon. + this.disable(addon); + } catch (Exception e) { + // Unhandled exception. We'll give a bit of debug here. + this.handleAddonError(addon, e); + // Disable addon. + this.disable(addon); + } + } } From c02e5662662496307f5a4a1d9f6d396f6d225819 Mon Sep 17 00:00:00 2001 From: Invvk <70810073+Invvk@users.noreply.github.com> Date: Fri, 1 Apr 2022 00:12:47 +0300 Subject: [PATCH 20/74] using java 16 syntax (#1958) --- .../blueprints/BlueprintClipboard.java | 13 ++++---- .../bentobox/blueprints/BlueprintPaster.java | 4 +-- .../dataobjects/BlueprintEntity.java | 31 +++++++++---------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java index 7d0c01b82..b6f4a9c5f 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java @@ -257,8 +257,8 @@ public class BlueprintClipboard { } // Banners - if (blockState instanceof Banner) { - b.setBannerPatterns(((Banner) blockState).getPatterns()); + if (blockState instanceof Banner banner) { + b.setBannerPatterns(banner.getPatterns()); } return b; @@ -282,8 +282,8 @@ public class BlueprintClipboard { BlueprintEntity bpe = new BlueprintEntity(); bpe.setType(entity.getType()); bpe.setCustomName(entity.getCustomName()); - if (entity instanceof Villager) { - setVillager(entity, bpe); + if (entity instanceof Villager villager) { + setVillager(villager, bpe); } if (entity instanceof Colorable c) { if (c.getColor() != null) { @@ -321,11 +321,10 @@ public class BlueprintClipboard { /** * Set the villager stats - * @param entity - villager + * @param v - villager * @param bpe - Blueprint Entity */ - private void setVillager(LivingEntity entity, BlueprintEntity bpe) { - Villager v = (Villager)entity; + private void setVillager(Villager v, BlueprintEntity bpe) { bpe.setExperience(v.getVillagerExperience()); bpe.setLevel(v.getVillagerLevel()); bpe.setProfession(v.getProfession()); diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java index c91cc14b0..a3be6906c 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java @@ -305,8 +305,8 @@ public class BlueprintPaster { writeSign(block, bpBlock.getSignLines(), bpBlock.isGlowingText()); } // Chests, in general - if (bs instanceof InventoryHolder) { - Inventory ih = ((InventoryHolder)bs).getInventory(); + 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(ih::setItem); diff --git a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java index e173f6b86..6a5927ddb 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java @@ -56,23 +56,23 @@ public class BlueprintEntity { * @since 1.8.0 */ public void configureEntity(Entity e) { - if (e instanceof Villager) { - setVillager(e); + if (e instanceof Villager villager) { + setVillager(villager); } - if (e instanceof Colorable) { - ((Colorable) e).setColor(color); + if (e instanceof Colorable c) { + c.setColor(color); } - if (tamed != null && e instanceof Tameable) { - ((Tameable)e).setTamed(tamed); + if (tamed != null && e instanceof Tameable tameable) { + tameable.setTamed(tamed); } - if (chest != null && e instanceof ChestedHorse) { - ((ChestedHorse)e).setCarryingChest(chest); + if (chest != null && e instanceof ChestedHorse chestedHorse) { + chestedHorse.setCarryingChest(chest); } - if (adult != null && e instanceof Ageable) { + if (adult != null && e instanceof Ageable ageable) { if (adult) { - ((Ageable)e).setAdult(); + ageable.setAdult(); } else { - ((Ageable)e).setBaby(); + ageable.setBaby(); } } if (e instanceof AbstractHorse horse) { @@ -81,18 +81,17 @@ public class BlueprintEntity { inventory.forEach(horse.getInventory()::setItem); } } - if (style != null && e instanceof Horse) { - ((Horse)e).setStyle(style); + if (style != null && e instanceof Horse horse) { + horse.setStyle(style); } } /** - * @param e - villager + * @param v - villager * @since 1.16.0 */ - private void setVillager(Entity e) { - Villager v = (Villager)e; + private void setVillager(Villager v) { v.setProfession(profession == null ? Profession.NONE : profession); v.setVillagerExperience(experience == null ? 0 : experience); v.setVillagerLevel(level == null ? 0 : level); From ad0931ffcbb22755e5398f1351467cd224c8bb62 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sun, 3 Apr 2022 08:12:41 +0300 Subject: [PATCH 21/74] Fixes kick command (#1960) PR #1957 broke kick command and noone could kick players from teams. This should fix it. --- .../island/team/IslandTeamKickCommand.java | 2 +- .../team/IslandTeamKickCommandTest.java | 74 ++++++++++++++++++- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java index e89bc3594..44ca46783 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java @@ -69,7 +69,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand { return false; } - int targetRank = Objects.requireNonNull(island).getRank(user); + int targetRank = Objects.requireNonNull(island).getRank(targetUUID); if (rank <= targetRank) { user.sendMessage("commands.island.team.kick.cannot-kick-rank", TextVariables.NAME, getPlayers().getName(targetUUID)); diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java index 3e1c3ac28..b51cec80c 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java @@ -184,6 +184,10 @@ public class IslandTeamKickCommandTest { RanksManager rm = new RanksManager(); when(plugin.getRanksManager()).thenReturn(rm); + // Ranks + when(island.getRank(uuid)).thenReturn(RanksManager.OWNER_RANK); + when(island.getRank(user)).thenReturn(RanksManager.OWNER_RANK); + when(island.getRank(notUUID)).thenReturn(RanksManager.MEMBER_RANK); } @After @@ -206,11 +210,75 @@ public class IslandTeamKickCommandTest { * Test method for {@link IslandTeamKickCommand#execute(User, String, java.util.List)} */ @Test - public void testExecuteNotTeamOwner() { - when(im.getOwner(any(), any())).thenReturn(notUUID); + public void testExecuteLowerTeamRank() { + when(island.getRank(user)).thenReturn(RanksManager.MEMBER_RANK); + when(island.getRank(notUUID)).thenReturn(RanksManager.SUB_OWNER_RANK); + + when(pm.getUUID(any())).thenReturn(notUUID); + when(pm.getName(notUUID)).thenReturn("poslovitch"); + + Set members = new HashSet<>(); + members.add(notUUID); + when(im.getMembers(any(), any())).thenReturn(members); + + IslandTeamKickCommand itl = new IslandTeamKickCommand(ic); + assertFalse(itl.execute(user, itl.getLabel(), Collections.singletonList("poslovitch"))); + verify(user).sendMessage(eq("commands.island.team.kick.cannot-kick-rank"), eq(TextVariables.NAME), eq("poslovitch")); + } + + + /** + * Test method for {@link IslandTeamKickCommand#execute(User, String, java.util.List)} + */ + @Test + public void testExecuteEqualTeamRank() { + when(island.getRank(user)).thenReturn(RanksManager.SUB_OWNER_RANK); + when(island.getRank(notUUID)).thenReturn(RanksManager.SUB_OWNER_RANK); + + when(pm.getUUID(any())).thenReturn(notUUID); + when(pm.getName(notUUID)).thenReturn("poslovitch"); + + Set members = new HashSet<>(); + members.add(notUUID); + when(im.getMembers(any(), any())).thenReturn(members); + + IslandTeamKickCommand itl = new IslandTeamKickCommand(ic); + assertFalse(itl.execute(user, itl.getLabel(), Collections.singletonList("poslovitch"))); + verify(user).sendMessage(eq("commands.island.team.kick.cannot-kick-rank"), eq(TextVariables.NAME), eq("poslovitch")); + } + + /** + * Test method for {@link IslandTeamKickCommand#execute(User, String, java.util.List)} + */ + @Test + public void testExecuteLargerTeamRank() { + when(island.getRank(user)).thenReturn(RanksManager.SUB_OWNER_RANK); + when(island.getRank(notUUID)).thenReturn(RanksManager.MEMBER_RANK); + + when(pm.getUUID(any())).thenReturn(notUUID); + when(pm.getName(notUUID)).thenReturn("poslovitch"); + + Set members = new HashSet<>(); + members.add(notUUID); + when(im.getMembers(any(), any())).thenReturn(members); + + IslandTeamKickCommand itl = new IslandTeamKickCommand(ic); + assertTrue(itl.execute(user, itl.getLabel(), Collections.singletonList("poslovitch"))); + verify(im).removePlayer(any(), eq(notUUID)); + verify(user).sendMessage("commands.island.team.kick.success", TextVariables.NAME, "poslovitch"); + } + + /** + * Test method for {@link IslandTeamKickCommand#execute(User, String, java.util.List)} + */ + @Test + public void testExecuteNoCommandRank() { + when(island.getRankCommand(anyString())).thenReturn(RanksManager.SUB_OWNER_RANK); + when(island.getRank(user)).thenReturn(RanksManager.MEMBER_RANK); + IslandTeamKickCommand itl = new IslandTeamKickCommand(ic); assertFalse(itl.execute(user, itl.getLabel(), Collections.emptyList())); - verify(user).sendMessage(eq("general.errors.not-owner")); + verify(user).sendMessage(eq("general.errors.insufficient-rank"), eq(TextVariables.RANK), eq("ranks.member")); } /** From 4341c28aca67935defc50c561f0279e546507bb0 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sun, 3 Apr 2022 21:36:51 +0300 Subject: [PATCH 22/74] Fixes a bug with blueprint height (#1961) Blueprint clipboard was preventing setting Y below 0 or above 255. The code was not adjusted to 1.18 changes. Reported via discord. --- .../blueprints/BlueprintClipboard.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java index b6f4a9c5f..a3daa230c 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java @@ -370,11 +370,17 @@ public class BlueprintClipboard { public void setPos1(@Nullable Location pos1) { origin = null; if (pos1 != null) { - if (pos1.getBlockY() < 0) { - pos1.setY(0); + final int minHeight = pos1.getWorld() == null ? 0 : pos1.getWorld().getMinHeight(); + final int maxHeight = pos1.getWorld() == null ? 255 : pos1.getWorld().getMaxHeight(); + + if (pos1.getBlockY() < minHeight) + { + pos1.setY(minHeight); } - if (pos1.getBlockY() > 255) { - pos1.setY(255); + + if (pos1.getBlockY() > maxHeight) + { + pos1.setY(maxHeight); } } this.pos1 = pos1; @@ -386,11 +392,17 @@ public class BlueprintClipboard { public void setPos2(@Nullable Location pos2) { origin = null; if (pos2 != null) { - if (pos2.getBlockY() < 0) { - pos2.setY(0); + final int minHeight = pos2.getWorld() == null ? 0 : pos2.getWorld().getMinHeight(); + final int maxHeight = pos2.getWorld() == null ? 255 : pos2.getWorld().getMaxHeight(); + + if (pos2.getBlockY() < minHeight) + { + pos2.setY(minHeight); } - if (pos2.getBlockY() > 255) { - pos2.setY(255); + + if (pos2.getBlockY() > maxHeight) + { + pos2.setY(maxHeight); } } this.pos2 = pos2; From 9f163f05723b56ad18e082a63133cb53b99984d4 Mon Sep 17 00:00:00 2001 From: BONNe Date: Mon, 11 Apr 2022 08:13:12 +0300 Subject: [PATCH 23/74] Fixes Lava Duplication Glitch (#1964) Due to the fact, that Obsidian Scooping uses one tick delay to remove obsidian, a player with a bucket in hand and offhand duplicated lava. To avoid that, added an extra check that ignores the interact event if a player holds a bucket in both hands, and interacted hand is offhand. Fixes #1963 --- .../flags/worldsettings/ObsidianScoopingListener.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListener.java index df67b0a5c..0900d396a 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListener.java @@ -15,6 +15,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import world.bentobox.bentobox.BentoBox; @@ -54,6 +55,16 @@ public class ObsidianScoopingListener extends FlagListener { || e.getClickedBlock().getRelative(e.getBlockFace()).getType().equals(Material.WATER)) { return false; } + + if (Material.BUCKET.equals(e.getPlayer().getInventory().getItemInOffHand().getType()) && + Material.BUCKET.equals(e.getPlayer().getInventory().getItemInMainHand().getType()) && + EquipmentSlot.OFF_HAND.equals(e.getHand())) + { + // If player is holding bucket in both hands, then allow to interact only with main hand. + // Prevents lava duplication glitch. + return false; + } + return lookForLava(e); } From b3e55a7b558f523e98004c796fa2e9d476520d20 Mon Sep 17 00:00:00 2001 From: BONNe Date: Mon, 11 Apr 2022 08:23:13 +0300 Subject: [PATCH 24/74] Fixes failures in obsidian cooping listener. (#1965) Failures happened after implementing #1964 --- .../flags/worldsettings/ObsidianScoopingListenerTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListenerTest.java index 73f134462..8eb31b5b0 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListenerTest.java @@ -153,6 +153,11 @@ public class ObsidianScoopingListenerTest { map.put("OBSIDIAN_SCOOPING", true); when(ws.getWorldFlags()).thenReturn(map); + PlayerInventory playerInventory = mock(PlayerInventory.class); + when(playerInventory.getItemInMainHand()).thenReturn(item); + when(playerInventory.getItemInOffHand()).thenReturn(new ItemStack(Material.AIR)); + when(p.getInventory()).thenReturn(playerInventory); + // Addon when(iwm.getAddon(Mockito.any())).thenReturn(Optional.empty()); } From c4c51d00e26cb10538e1491308afe136e066456d Mon Sep 17 00:00:00 2001 From: BONNe Date: Tue, 26 Apr 2022 12:06:50 +0300 Subject: [PATCH 25/74] Fixes wrong protection bounding box (#1971) #1966 fixed a bounding box size for the whole island, while the protection bounding box was still wrong. Fixes https://github.com/BentoBoxWorld/BSkyBlock/issues/473 --- .../world/bentobox/bentobox/database/objects/Island.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index 4b463fe33..b1b0f4f6c 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -803,7 +803,7 @@ public class Island implements DataObject, MetaDataAble { * @since 1.5.2 */ public BoundingBox getProtectionBoundingBox() { - return new BoundingBox(getMinProtectedX(), 0.0D, getMinProtectedZ(), getMaxProtectedX()-1.0D, world.getMaxHeight(), getMaxProtectedZ()-1.0D); + return new BoundingBox(getMinProtectedX(), world.getMinHeight(), getMinProtectedZ(), getMaxProtectedX()-1.0D, world.getMaxHeight(), getMaxProtectedZ()-1.0D); } /** @@ -1661,9 +1661,4 @@ public class Island implements DataObject, MetaDataAble { + ", cooldowns=" + cooldowns + ", commandRanks=" + commandRanks + ", reserved=" + reserved + ", metaData=" + metaData + ", homes=" + homes + ", maxHomes=" + maxHomes + "]"; } - - - - - } From 385aed02cda2f82e98ae2f3e023b6ca0c6678081 Mon Sep 17 00:00:00 2001 From: BONNe Date: Tue, 26 Apr 2022 12:07:44 +0300 Subject: [PATCH 26/74] Update to next BentoBox version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 329505f39..5047cd7ca 100644 --- a/pom.xml +++ b/pom.xml @@ -83,7 +83,7 @@ -LOCAL - 1.20.1 + 1.21.0 bentobox-world https://sonarcloud.io From 3e0368fbab0968a131d7a84dd6a58e4ed9ca8b97 Mon Sep 17 00:00:00 2001 From: Huynh Tien Date: Thu, 28 Apr 2022 19:39:39 +0700 Subject: [PATCH 27/74] fix null on invitedPlayer (#1972) --- .../api/commands/island/team/IslandTeamInviteCommand.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java index 5fb312096..2785fb95c 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java @@ -109,6 +109,8 @@ public class IslandTeamInviteCommand extends CompositeCommand { @Override public boolean execute(User user, String label, List args) { + // Rare case when invited player is null. Could be a race condition. + if (invitedPlayer == null) return false; // If that player already has an invite out then retract it. // Players can only have one invite one at a time - interesting if (itc.isInvited(invitedPlayer.getUniqueId())) { From d07c1b5a8c897d0bc95227e96ad43e51a70b2b9c Mon Sep 17 00:00:00 2001 From: evlad Date: Sun, 1 May 2022 07:44:42 +0200 Subject: [PATCH 28/74] support 1.18 negative y (#1973) Fixes an issue where blueprint starting block could not be placed in negative area. --- .../world/bentobox/bentobox/managers/IslandWorldManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java index 72405a848..eff2e8c65 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java @@ -229,13 +229,13 @@ public class IslandWorldManager { } /** - * Value will always be greater than 0 and less than the world's max height. + * Value will always be greater than the world's min height and less than the world's max height. * @return the islandHeight */ public int getIslandHeight(@NonNull World world) { if (gameModes.containsKey(world) && world.getMaxHeight() > 0) { return Math.min(world.getMaxHeight() - 1, - Math.max(0, gameModes.get(world).getWorldSettings().getIslandHeight())); + Math.max(world.getMinHeight(), gameModes.get(world).getWorldSettings().getIslandHeight())); } return 0; } From 6fba4bfbea983c47125eeb6109433d95a1fa0303 Mon Sep 17 00:00:00 2001 From: Huynh Tien Date: Tue, 3 May 2022 05:23:31 +0700 Subject: [PATCH 29/74] More abstract on World Regenerator (#1969) * more abstract on delete chunks * update NMS to 1.18.2 * at most abstract, requires only the island and the world * it's weird that we can't use whenComplete * rename to WorldRegenerator --- pom.xml | 2 +- .../bentobox/bentobox/nms/NMSAbstraction.java | 49 ------- .../bentobox/nms/SimpleWorldRegenerator.java | 137 ++++++++++++++++++ .../bentobox/nms/WorldRegenerator.java | 22 +++ .../bentobox/nms/fallback/NMSHandler.java | 20 --- .../nms/fallback/WorldRegeneratorImpl.java | 20 +++ .../WorldRegeneratorImpl.java} | 10 +- .../bentobox/util/DeleteIslandChunks.java | 113 ++++----------- .../world/bentobox/bentobox/util/Util.java | 40 +++-- 9 files changed, 238 insertions(+), 175 deletions(-) delete mode 100644 src/main/java/world/bentobox/bentobox/nms/NMSAbstraction.java create mode 100644 src/main/java/world/bentobox/bentobox/nms/SimpleWorldRegenerator.java create mode 100644 src/main/java/world/bentobox/bentobox/nms/WorldRegenerator.java delete mode 100644 src/main/java/world/bentobox/bentobox/nms/fallback/NMSHandler.java create mode 100644 src/main/java/world/bentobox/bentobox/nms/fallback/WorldRegeneratorImpl.java rename src/main/java/world/bentobox/bentobox/nms/{v1_18_R1/NMSHandler.java => v1_18_R2/WorldRegeneratorImpl.java} (78%) diff --git a/pom.xml b/pom.xml index 5047cd7ca..c4be8f5b6 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 2.0.9 3.12.8 - 1.18-R0.1-SNAPSHOT + 1.18.2-R0.1-SNAPSHOT 1.16.5-R0.1-SNAPSHOT diff --git a/src/main/java/world/bentobox/bentobox/nms/NMSAbstraction.java b/src/main/java/world/bentobox/bentobox/nms/NMSAbstraction.java deleted file mode 100644 index 40e0f4e97..000000000 --- a/src/main/java/world/bentobox/bentobox/nms/NMSAbstraction.java +++ /dev/null @@ -1,49 +0,0 @@ -package world.bentobox.bentobox.nms; - -import org.bukkit.Chunk; -import org.bukkit.block.data.BlockData; -import org.bukkit.generator.ChunkGenerator; -import org.bukkit.util.BoundingBox; - -public interface NMSAbstraction { - /** - * Copy the chunk data and biome grid to the given chunk. - * @param chunk - chunk to copy to - * @param chunkData - chunk data to copy - * @param biomeGrid - biome grid to copy to - * @param limitBox - bounding box to limit the copying - */ - default void copyChunkDataToChunk(Chunk chunk, ChunkGenerator.ChunkData chunkData, ChunkGenerator.BiomeGrid biomeGrid, BoundingBox limitBox) { - double baseX = chunk.getX() << 4; - double baseZ = chunk.getZ() << 4; - int minHeight = chunk.getWorld().getMinHeight(); - int maxHeight = chunk.getWorld().getMaxHeight(); - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - if (!limitBox.contains(baseX + x, 0, baseZ + z)) { - continue; - } - for (int y = minHeight; y < maxHeight; y++) { - setBlockInNativeChunk(chunk, x, y, z, chunkData.getBlockData(x, y, z), false); - // 3D biomes, 4 blocks separated - if (x % 4 == 0 && y % 4 == 0 && z % 4 == 0) { - chunk.getBlock(x, y, z).setBiome(biomeGrid.getBiome(x, y, z)); - } - } - } - } - } - - /** - * Update the low-level chunk information for the given block to the new block ID and data. This - * change will not be propagated to clients until the chunk is refreshed to them. - * @param chunk - chunk to be changed - * @param x - x coordinate within chunk 0 - 15 - * @param y - y coordinate within chunk 0 - world height, e.g. 255 - * @param z - z coordinate within chunk 0 - 15 - * @param blockData - block data to set the block - * @param applyPhysics - apply physics or not - */ - void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics); - -} diff --git a/src/main/java/world/bentobox/bentobox/nms/SimpleWorldRegenerator.java b/src/main/java/world/bentobox/bentobox/nms/SimpleWorldRegenerator.java new file mode 100644 index 000000000..86075a382 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/nms/SimpleWorldRegenerator.java @@ -0,0 +1,137 @@ +package world.bentobox.bentobox.nms; + +import io.papermc.lib.PaperLib; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.BoundingBox; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.database.objects.IslandDeletion; +import world.bentobox.bentobox.util.MyBiomeGrid; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CompletableFuture; + +public abstract class SimpleWorldRegenerator implements WorldRegenerator { + private final BentoBox plugin; + + protected SimpleWorldRegenerator() { + this.plugin = BentoBox.getInstance(); + } + + /** + * Update the low-level chunk information for the given block to the new block ID and data. This + * change will not be propagated to clients until the chunk is refreshed to them. + * + * @param chunk - chunk to be changed + * @param x - x coordinate within chunk 0 - 15 + * @param y - y coordinate within chunk 0 - world height, e.g. 255 + * @param z - z coordinate within chunk 0 - 15 + * @param blockData - block data to set the block + * @param applyPhysics - apply physics or not + */ + protected abstract void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics); + + @Override + public CompletableFuture regenerate(GameModeAddon gm, IslandDeletion di, World world) { + CompletableFuture bigFuture = new CompletableFuture<>(); + new BukkitRunnable() { + private int chunkX = di.getMinXChunk(); + private int chunkZ = di.getMinZChunk(); + CompletableFuture currentTask = CompletableFuture.completedFuture(null); + + @Override + public void run() { + if (!currentTask.isDone()) return; + if (isEnded(chunkX)) { + cancel(); + bigFuture.complete(null); + return; + } + List> newTasks = new ArrayList<>(); + for (int i = 0; i < plugin.getSettings().getDeleteSpeed(); i++) { + if (isEnded(chunkX)) { + break; + } + final int x = chunkX; + final int z = chunkZ; + newTasks.add(regenerateChunk(gm, di, world, x, z)); + chunkZ++; + if (chunkZ > di.getMaxZChunk()) { + chunkZ = di.getMinZChunk(); + chunkX++; + } + } + currentTask = CompletableFuture.allOf(newTasks.toArray(new CompletableFuture[0])); + } + + private boolean isEnded(int chunkX) { + return chunkX > di.getMaxXChunk(); + } + }.runTaskTimer(plugin, 0L, 20L); + return bigFuture; + } + + @SuppressWarnings("deprecation") + private CompletableFuture regenerateChunk(GameModeAddon gm, IslandDeletion di, World world, int chunkX, int chunkZ) { + CompletableFuture chunkFuture = PaperLib.getChunkAtAsync(world, chunkX, chunkZ); + CompletableFuture invFuture = chunkFuture.thenAccept(chunk -> + Arrays.stream(chunk.getTileEntities()).filter(InventoryHolder.class::isInstance) + .filter(te -> di.inBounds(te.getLocation().getBlockX(), te.getLocation().getBlockZ())) + .forEach(te -> ((InventoryHolder) te).getInventory().clear()) + ); + CompletableFuture entitiesFuture = chunkFuture.thenAccept(chunk -> { + for (Entity e : chunk.getEntities()) { + if (!(e instanceof Player)) { + e.remove(); + } + } + }); + CompletableFuture copyFuture = chunkFuture.thenApply(chunk -> { + // Reset blocks + MyBiomeGrid grid = new MyBiomeGrid(chunk.getWorld().getEnvironment()); + ChunkGenerator cg = gm.getDefaultWorldGenerator(chunk.getWorld().getName(), "delete"); + // Will be null if use-own-generator is set to true + if (cg != null) { + ChunkGenerator.ChunkData cd = cg.generateChunkData(chunk.getWorld(), new Random(), chunk.getX(), chunk.getZ(), grid); + copyChunkDataToChunk(chunk, cd, grid, di.getBox()); + } + return chunk; + }); + CompletableFuture postCopyFuture = copyFuture.thenAccept(chunk -> { + // Remove all entities in chunk, including any dropped items as a result of clearing the blocks above + Arrays.stream(chunk.getEntities()).filter(e -> !(e instanceof Player) && di.inBounds(e.getLocation().getBlockX(), e.getLocation().getBlockZ())).forEach(Entity::remove); + }); + return CompletableFuture.allOf(invFuture, entitiesFuture, postCopyFuture); + } + + private void copyChunkDataToChunk(Chunk chunk, ChunkGenerator.ChunkData chunkData, ChunkGenerator.BiomeGrid biomeGrid, BoundingBox limitBox) { + double baseX = chunk.getX() << 4; + double baseZ = chunk.getZ() << 4; + int minHeight = chunk.getWorld().getMinHeight(); + int maxHeight = chunk.getWorld().getMaxHeight(); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + if (!limitBox.contains(baseX + x, 0, baseZ + z)) { + continue; + } + for (int y = minHeight; y < maxHeight; y++) { + setBlockInNativeChunk(chunk, x, y, z, chunkData.getBlockData(x, y, z), false); + // 3D biomes, 4 blocks separated + if (x % 4 == 0 && y % 4 == 0 && z % 4 == 0) { + chunk.getBlock(x, y, z).setBiome(biomeGrid.getBiome(x, y, z)); + } + } + } + } + } +} diff --git a/src/main/java/world/bentobox/bentobox/nms/WorldRegenerator.java b/src/main/java/world/bentobox/bentobox/nms/WorldRegenerator.java new file mode 100644 index 000000000..a0bafb545 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/nms/WorldRegenerator.java @@ -0,0 +1,22 @@ +package world.bentobox.bentobox.nms; + +import org.bukkit.World; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.database.objects.IslandDeletion; + +import java.util.concurrent.CompletableFuture; + +/** + * A world generator used by {@link world.bentobox.bentobox.util.DeleteIslandChunks} + */ +public interface WorldRegenerator { + /** + * Create a future to regenerate the regions of the island. + * + * @param gm the game mode + * @param di the island deletion + * @param world the world + * @return the completable future + */ + CompletableFuture regenerate(GameModeAddon gm, IslandDeletion di, World world); +} diff --git a/src/main/java/world/bentobox/bentobox/nms/fallback/NMSHandler.java b/src/main/java/world/bentobox/bentobox/nms/fallback/NMSHandler.java deleted file mode 100644 index 23285dc4b..000000000 --- a/src/main/java/world/bentobox/bentobox/nms/fallback/NMSHandler.java +++ /dev/null @@ -1,20 +0,0 @@ -package world.bentobox.bentobox.nms.fallback; - -import org.bukkit.Chunk; -import org.bukkit.block.data.BlockData; - -import world.bentobox.bentobox.nms.NMSAbstraction; - -/** - * @author tastybento - * - */ -public class NMSHandler implements NMSAbstraction { - - @Override - public void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics) { - chunk.getBlock(x, y, z).setBlockData(blockData, applyPhysics); - } - - -} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/nms/fallback/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/fallback/WorldRegeneratorImpl.java new file mode 100644 index 000000000..60d356359 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/nms/fallback/WorldRegeneratorImpl.java @@ -0,0 +1,20 @@ +package world.bentobox.bentobox.nms.fallback; + +import org.bukkit.Chunk; +import org.bukkit.block.data.BlockData; + +import world.bentobox.bentobox.nms.SimpleWorldRegenerator; + +/** + * @author tastybento + * + */ +public class WorldRegeneratorImpl extends SimpleWorldRegenerator { + + @Override + protected void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics) { + chunk.getBlock(x, y, z).setBlockData(blockData, applyPhysics); + } + + +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_18_R1/NMSHandler.java b/src/main/java/world/bentobox/bentobox/nms/v1_18_R2/WorldRegeneratorImpl.java similarity index 78% rename from src/main/java/world/bentobox/bentobox/nms/v1_18_R1/NMSHandler.java rename to src/main/java/world/bentobox/bentobox/nms/v1_18_R2/WorldRegeneratorImpl.java index f0585bde3..6754ecd02 100644 --- a/src/main/java/world/bentobox/bentobox/nms/v1_18_R1/NMSHandler.java +++ b/src/main/java/world/bentobox/bentobox/nms/v1_18_R2/WorldRegeneratorImpl.java @@ -1,19 +1,19 @@ -package world.bentobox.bentobox.nms.v1_18_R1; +package world.bentobox.bentobox.nms.v1_18_R2; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_18_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_18_R1.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_18_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_18_R2.block.data.CraftBlockData; import net.minecraft.core.BlockPosition; import net.minecraft.world.level.World; import net.minecraft.world.level.block.state.IBlockData; import net.minecraft.world.level.chunk.Chunk; -import world.bentobox.bentobox.nms.NMSAbstraction; +import world.bentobox.bentobox.nms.SimpleWorldRegenerator; -public class NMSHandler implements NMSAbstraction { +public class WorldRegeneratorImpl extends SimpleWorldRegenerator { private static final IBlockData AIR = ((CraftBlockData) Bukkit.createBlockData(Material.AIR)).getState(); diff --git a/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java b/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java index 0f1c88602..a9be0f62b 100644 --- a/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java +++ b/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java @@ -1,29 +1,16 @@ package world.bentobox.bentobox.util; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.bukkit.Bukkit; -import org.bukkit.Chunk; import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.generator.ChunkGenerator; -import org.bukkit.generator.ChunkGenerator.ChunkData; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.scheduler.BukkitTask; - -import io.papermc.lib.PaperLib; +import org.bukkit.scheduler.BukkitRunnable; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.events.island.IslandEvent; import world.bentobox.bentobox.api.events.island.IslandEvent.Reason; import world.bentobox.bentobox.database.objects.IslandDeletion; -import world.bentobox.bentobox.nms.NMSAbstraction; +import world.bentobox.bentobox.nms.WorldRegenerator; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; /** * Deletes islands chunk by chunk @@ -37,16 +24,10 @@ public class DeleteIslandChunks { private final World netherWorld; private final World endWorld; private final AtomicBoolean completed; - private final NMSAbstraction nms; - private int chunkX; - private int chunkZ; - private BukkitTask task; - private CompletableFuture currentTask = CompletableFuture.completedFuture(null); + private final WorldRegenerator regenerator; public DeleteIslandChunks(BentoBox plugin, IslandDeletion di) { this.plugin = plugin; - this.chunkX = di.getMinXChunk(); - this.chunkZ = di.getMinZChunk(); this.di = di; completed = new AtomicBoolean(false); // Nether @@ -61,9 +42,9 @@ public class DeleteIslandChunks { } else { endWorld = null; } - // NMS - this.nms = Util.getNMS(); - if (nms == null) { + // Regenerator + this.regenerator = Util.getRegenerator(); + if (regenerator == null) { plugin.logError("Could not delete chunks because of NMS error"); return; } @@ -75,37 +56,23 @@ public class DeleteIslandChunks { } private void regenerateChunks() { - // Run through all chunks of the islands and regenerate them. - task = Bukkit.getScheduler().runTaskTimer(plugin, () -> { - if (!currentTask.isDone()) return; - if (isEnded(chunkX)) { - finish(); - return; - } - List> newTasks = new ArrayList<>(); - for (int i = 0; i < plugin.getSettings().getDeleteSpeed(); i++) { - if (isEnded(chunkX)) { - break; - } - final int x = chunkX; - final int z = chunkZ; - plugin.getIWM().getAddon(di.getWorld()).ifPresent(gm -> { - newTasks.add(processChunk(gm, di.getWorld(), x, z)); // Overworld - newTasks.add(processChunk(gm, netherWorld, x, z)); // Nether - newTasks.add(processChunk(gm, endWorld, x, z)); // End - }); - chunkZ++; - if (chunkZ > di.getMaxZChunk()) { - chunkZ = di.getMinZChunk(); - chunkX++; + CompletableFuture all = plugin.getIWM().getAddon(di.getWorld()) + .map(gm -> new CompletableFuture[]{ + processWorld(gm, di.getWorld()), // Overworld + processWorld(gm, netherWorld), // Nether + processWorld(gm, endWorld) // End + }) + .map(CompletableFuture::allOf) + .orElseGet(() -> CompletableFuture.completedFuture(null)); + new BukkitRunnable() { + @Override + public void run() { + if (all.isDone()) { + finish(); + cancel(); } } - currentTask = CompletableFuture.allOf(newTasks.toArray(new CompletableFuture[0])); - }, 0L, 20L); - } - - private boolean isEnded(int chunkX) { - return chunkX > di.getMaxXChunk(); + }.runTaskTimer(plugin, 0, 20); } private void finish() { @@ -113,44 +80,16 @@ public class DeleteIslandChunks { IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETED).build(); // We're done completed.set(true); - task.cancel(); } - private CompletableFuture processChunk(GameModeAddon gm, World world, int x, int z) { + private CompletableFuture processWorld(GameModeAddon gm, World world) { if (world != null) { - return PaperLib.getChunkAtAsync(world, x, z).thenAccept(chunk -> regenerateChunk(gm, chunk, x, z)); + return regenerator.regenerate(gm, di, world); } else { return CompletableFuture.completedFuture(null); } } - private void regenerateChunk(GameModeAddon gm, Chunk chunk, int x, int z) { - // Clear all inventories - Arrays.stream(chunk.getTileEntities()).filter(InventoryHolder.class::isInstance) - .filter(te -> di.inBounds(te.getLocation().getBlockX(), te.getLocation().getBlockZ())) - .forEach(te -> ((InventoryHolder) te).getInventory().clear()); - // Remove all entities - for (Entity e : chunk.getEntities()) { - if (!(e instanceof Player)) { - e.remove(); - } - } - // Reset blocks - MyBiomeGrid grid = new MyBiomeGrid(chunk.getWorld().getEnvironment()); - ChunkGenerator cg = gm.getDefaultWorldGenerator(chunk.getWorld().getName(), "delete"); - // Will be null if use-own-generator is set to true - if (cg != null) { - ChunkData cd = cg.generateChunkData(chunk.getWorld(), new Random(), chunk.getX(), chunk.getZ(), grid); - createChunk(cd, chunk, grid); - } - } - - private void createChunk(ChunkData cd, Chunk chunk, MyBiomeGrid grid) { - nms.copyChunkDataToChunk(chunk, cd, grid, di.getBox()); - // Remove all entities in chunk, including any dropped items as a result of clearing the blocks above - Arrays.stream(chunk.getEntities()).filter(e -> !(e instanceof Player) && di.inBounds(e.getLocation().getBlockX(), e.getLocation().getBlockZ())).forEach(Entity::remove); - } - public boolean isCompleted() { return completed.get(); } diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java index f2ef681f8..c0a049119 100644 --- a/src/main/java/world/bentobox/bentobox/util/Util.java +++ b/src/main/java/world/bentobox/bentobox/util/Util.java @@ -47,7 +47,7 @@ import io.papermc.lib.PaperLib; import io.papermc.lib.features.blockstatesnapshot.BlockStateSnapshotResult; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.nms.NMSAbstraction; +import world.bentobox.bentobox.nms.WorldRegenerator; /** * A set of utility methods @@ -64,7 +64,7 @@ public class Util { private static final String THE_END = "_the_end"; private static String serverVersion = null; private static BentoBox plugin = BentoBox.getInstance(); - private static NMSAbstraction nms = null; + private static WorldRegenerator regenerator = null; private Util() {} @@ -689,23 +689,37 @@ public class Util { } /** - * Set the NMS handler the plugin will use - * @param nms the NMS handler + * Set the regenerator the plugin will use + * @param regenerator the regenerator */ - public static void setNms(NMSAbstraction nms) { - Util.nms = nms; + public static void setRegenerator(WorldRegenerator regenerator) { + Util.regenerator = regenerator; } /** - * Get the NMS handler the plugin will use - * @return an NMS accelerated class for this server + * Get the regenerator the plugin will use + * @return an accelerated regenerator class for this server */ - public static NMSAbstraction getNMS() { - if (nms == null) { - plugin.log("No NMS Handler was set, falling back to Bukkit API."); - setNms(new world.bentobox.bentobox.nms.fallback.NMSHandler()); + public static WorldRegenerator getRegenerator() { + if (regenerator == null) { + String serverPackageName = Bukkit.getServer().getClass().getPackage().getName(); + String pluginPackageName = plugin.getClass().getPackage().getName(); + String version = serverPackageName.substring(serverPackageName.lastIndexOf('.') + 1); + WorldRegenerator handler; + try { + Class clazz = Class.forName(pluginPackageName + ".nms." + version + ".WorldRegeneratorImpl"); + if (WorldRegenerator.class.isAssignableFrom(clazz)) { + handler = (WorldRegenerator) clazz.getConstructor().newInstance(); + } else { + throw new IllegalStateException("Class " + clazz.getName() + " does not implement WorldRegenerator"); + } + } catch (Exception e) { + plugin.logWarning("No Regenerator found for " + version + ", falling back to Bukkit API."); + handler = new world.bentobox.bentobox.nms.fallback.WorldRegeneratorImpl(); + } + setRegenerator(handler); } - return nms; + return regenerator; } /** From 4ab579f2cd754e9ab5e2c01040880fb8eb595892 Mon Sep 17 00:00:00 2001 From: evlad Date: Tue, 3 May 2022 07:03:49 +0200 Subject: [PATCH 30/74] Feat: Filtering spectators from visitors (#1974) * support 1.18 negative y * feat: visitor gamemode spectator check --- .../bentobox/database/objects/Island.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index b1b0f4f6c..57dff7e6b 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -18,6 +18,7 @@ import java.util.stream.Collectors; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; +import org.bukkit.GameMode; import org.bukkit.World.Environment; import org.bukkit.entity.Player; import org.bukkit.util.BoundingBox; @@ -701,6 +702,19 @@ public class Island implements DataObject, MetaDataAble { return new BoundingBox(getMinX(), world.getMinHeight(), getMinZ(), getMaxX(), world.getMaxHeight(), getMaxZ()); } + /** + * Using this method in the filtering for getVisitors and hasVisitors + * @param player + * @return true if player is a visitor + */ + private boolean playerIsVisitor(Player player) { + if (player.getGameMode() == GameMode.SPECTATOR) { + return false; + } + + return onIsland(player.getLocation()) && getRank(User.getInstance(player)) == RanksManager.VISITOR_RANK; + } + /** * Returns a list of players that are physically inside the island's protection range and that are visitors. * @return list of visitors @@ -709,7 +723,7 @@ public class Island implements DataObject, MetaDataAble { @NonNull public List getVisitors() { return Bukkit.getOnlinePlayers().stream() - .filter(player -> onIsland(player.getLocation()) && getRank(User.getInstance(player)) == RanksManager.VISITOR_RANK) + .filter(player -> playerIsVisitor(player)) .collect(Collectors.toList()); } @@ -722,7 +736,7 @@ public class Island implements DataObject, MetaDataAble { * @see #getVisitors() */ public boolean hasVisitors() { - return Bukkit.getOnlinePlayers().stream().anyMatch(player -> onIsland(player.getLocation()) && getRank(User.getInstance(player)) == RanksManager.VISITOR_RANK); + return Bukkit.getOnlinePlayers().stream().anyMatch(player -> playerIsVisitor(player)); } /** From 0f815d8175a7dc17710f6fcf2d0c601c2186deb7 Mon Sep 17 00:00:00 2001 From: Huynh Tien Date: Wed, 4 May 2022 00:39:27 +0700 Subject: [PATCH 31/74] More abstract on Blueprint Paster (#1970) * more abstract on Paster * comment and stuff * How about Impl * ok, PasteHandler * PasteUtil * don't check other BlockState if there is one matched * world as an argument * forgot the impl * createBlockData from BlueprintBlock * fix remaining conflicts Co-authored-by: tastybento --- .../bentobox/blueprints/BlueprintPaster.java | 310 +++++------------- .../bentobox/bentobox/nms/PasteHandler.java | 36 ++ .../nms/fallback/PasteHandlerImpl.java | 27 ++ .../bentobox/util/DefaultPasteUtil.java | 237 +++++++++++++ .../world/bentobox/bentobox/util/Util.java | 22 ++ 5 files changed, 396 insertions(+), 236 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/nms/PasteHandler.java create mode 100644 src/main/java/world/bentobox/bentobox/nms/fallback/PasteHandlerImpl.java create mode 100644 src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java index a3be6906c..2bbee20cc 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java @@ -1,49 +1,27 @@ package world.bentobox.bentobox.blueprints; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -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.data.BlockData; -import org.bukkit.block.data.type.Sign; -import org.bukkit.block.data.type.WallSign; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; import org.bukkit.scheduler.BukkitTask; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; - -import com.google.common.collect.ImmutableMap; - 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.nms.PasteHandler; import world.bentobox.bentobox.util.Util; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; + /** * This class pastes the clipboard it is given * @author tastybento @@ -68,11 +46,9 @@ public class BlueprintPaster { */ private static long chunkLoadTime = 0; - private static final String MINECRAFT = "minecraft:"; - - private static final Map BLOCK_CONVERSION = ImmutableMap.of("sign", "oak_sign", "wall_sign", "oak_wall_sign"); - private final BentoBox plugin; + private final PasteHandler paster = Util.getPasteHandler(); + private final World world; // The minimum block position (x,y,z) private Location pos1; // The maximum block position (x,y,z) @@ -80,6 +56,7 @@ public class BlueprintPaster { private PasteState pasteState; private BukkitTask pastingTask; private BlueprintClipboard clipboard; + private CompletableFuture currentTask = CompletableFuture.completedFuture(null); /** * The Blueprint to paste. @@ -111,6 +88,7 @@ public class BlueprintPaster { // Calculate location for pasting this.blueprint = Objects.requireNonNull(clipboard.getBlueprint(), "Clipboard cannot have a null Blueprint"); this.location = location; + this.world = location.getWorld(); this.island = null; // Paste @@ -128,6 +106,7 @@ public class BlueprintPaster { this.plugin = plugin; this.blueprint = bp; this.island = island; + this.world = world; // Offset due to bedrock Vector off = bp.getBedrock() != null ? bp.getBedrock() : new Vector(0,0,0); // Calculate location for pasting @@ -157,7 +136,8 @@ public class BlueprintPaster { // If this is an island OVERWORLD paste, get the island owner. final Optional owner = Optional.ofNullable(island) .filter(i -> location.getWorld().getEnvironment().equals(World.Environment.NORMAL)) - .map(i -> User.getInstance(i.getOwner())); + .map(Island::getOwner) + .map(User::getInstance); // Tell the owner we're pasting blocks and how much time it might take owner.ifPresent(user -> tellOwner(user, blocks.size(), attached.size(), entities.size(), plugin.getSettings().getPasteSpeed())); Bits bits = new Bits(blocks, attached, entities, @@ -169,6 +149,8 @@ public class BlueprintPaster { } private void pasterTask(CompletableFuture result, Optional owner, Bits bits) { + if (!currentTask.isDone()) return; + final int pasteSpeed = plugin.getSettings().getPasteSpeed(); long timer = System.currentTimeMillis(); @@ -176,7 +158,7 @@ public class BlueprintPaster { if (pasteState.equals(PasteState.CHUNK_LOAD)) { pasteState = PasteState.CHUNK_LOADING; // Load chunk - Util.getChunkAtAsync(location).thenRun(() -> { + currentTask = Util.getChunkAtAsync(location).thenRun(() -> { pasteState = PasteState.BLOCKS; long duration = System.currentTimeMillis() - timer; if (duration > chunkLoadTime) { @@ -184,34 +166,65 @@ public class BlueprintPaster { } }); } - while (pasteState.equals(PasteState.BLOCKS) && count < pasteSpeed && bits.it.hasNext()) { - pasteBlock(location, bits.it.next()); - count++; - } - while (pasteState.equals(PasteState.ATTACHMENTS) && count < pasteSpeed && bits.it2.hasNext()) { - pasteBlock(location, bits.it2.next()); - count++; - } - while (pasteState.equals(PasteState.ENTITIES) && count < pasteSpeed && bits.it3.hasNext()) { - pasteEntity(location, bits.it3.next()); - count++; - } - // STATE SHIFT - if (pasteState.equals(PasteState.BLOCKS) && !bits.it.hasNext()) { - // Blocks done - // Next paste attachments - pasteState = PasteState.ATTACHMENTS; - } - else if (pasteState.equals(PasteState.ATTACHMENTS) && !bits.it2.hasNext()) { - // Attachments done. Next paste entities - pasteState = PasteState.ENTITIES; - if (bits.entities.size() != 0) { - owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.entities", TextVariables.NUMBER, String.valueOf(bits.entities.size()))); + else if (pasteState.equals(PasteState.BLOCKS) || pasteState.equals(PasteState.ATTACHMENTS)) { + Iterator> it = pasteState.equals(PasteState.BLOCKS) ? bits.it : bits.it2; + if (it.hasNext()) { + Map blockMap = new HashMap<>(); + // Paste blocks + while (count < pasteSpeed) { + if (!it.hasNext()) { + break; + } + Entry entry = it.next(); + Location pasteTo = location.clone().add(entry.getKey()); + // pos1 and pos2 update + updatePos(pasteTo); + + BlueprintBlock block = entry.getValue(); + blockMap.put(pasteTo, block); + count++; + } + if (!blockMap.isEmpty()) { + currentTask = paster.pasteBlocks(island, world, blockMap); + } + } else { + if (pasteState.equals(PasteState.BLOCKS)) { + // Blocks done + // Next paste attachments + pasteState = PasteState.ATTACHMENTS; + } else { + // Attachments done. Next paste entities + pasteState = PasteState.ENTITIES; + if (bits.entities.size() != 0) { + owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.entities", TextVariables.NUMBER, String.valueOf(bits.entities.size()))); + } + } } } - else if (pasteState.equals(PasteState.ENTITIES) && !bits.it3.hasNext()) { - pasteState = PasteState.DONE; - owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.done")); + else if (pasteState.equals(PasteState.ENTITIES)) { + if (bits.it3().hasNext()) { + Map> entityMap = new HashMap<>(); + // Paste entities + while (count < pasteSpeed) { + if (!bits.it3().hasNext()) { + break; + } + Entry> entry = bits.it3().next(); + int x = location.getBlockX() + entry.getKey().getBlockX(); + int y = location.getBlockY() + entry.getKey().getBlockY(); + int z = location.getBlockZ() + entry.getKey().getBlockZ(); + Location center = new Location(world, x, y, z).add(new Vector(0.5, 0.5, 0.5)); + List entities = entry.getValue(); + entityMap.put(center, entities); + count++; + } + if (!entityMap.isEmpty()) { + currentTask = paster.pasteEntities(island, world, entityMap); + } + } else { + pasteState = PasteState.DONE; + owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.done")); + } } else if (pasteState.equals(PasteState.DONE)) { // All done. Cancel task @@ -238,135 +251,6 @@ public class BlueprintPaster { user.sendMessage("commands.island.create.pasting.blocks", TextVariables.NUMBER, String.valueOf(blocksSize + attachedSize)); } - private void pasteBlock(Location location, Entry entry) { - World world = location.getWorld(); - Location pasteTo = location.clone().add(entry.getKey()); - BlueprintBlock bpBlock = entry.getValue(); - Util.getChunkAtAsync(pasteTo).thenRun(() -> { - Block block = pasteTo.getBlock(); - // Set the block data - default is AIR - BlockData bd; - try { - bd = Bukkit.createBlockData(bpBlock.getBlockData()); - } catch (Exception e) { - bd = convertBlockData(world, bpBlock); - } - block.setBlockData(bd, false); - setBlockState(block, bpBlock); - // Set biome - if (bpBlock.getBiome() != null) { - block.setBiome(bpBlock.getBiome()); - } - // pos1 and pos2 update - updatePos(block.getLocation()); - }); - } - - /** - * Tries to convert the BlockData to a newer version, and logs a warning if it fails to do so. - * @return the converted BlockData or a default AIR BlockData. - * @since 1.6.0 - */ - private BlockData convertBlockData(World world, BlueprintBlock block) { - BlockData blockData = Bukkit.createBlockData(Material.AIR); - try { - for (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("World: " + world.getName() + "; Failed block data: " + block.getBlockData()); - } - return blockData; - } - - private void pasteEntity(Location location, Entry> entry) { - int x = location.getBlockX() + entry.getKey().getBlockX(); - int y = location.getBlockY() + entry.getKey().getBlockY(); - int z = location.getBlockZ() + entry.getKey().getBlockZ(); - setEntity(new Location(location.getWorld(), x, y, z), entry.getValue()); - } - - /** - * Handles signs, chests and mob spawner blocks - * @param block - block - * @param bpBlock - config - */ - private void setBlockState(Block block, BlueprintBlock bpBlock) { - // Get the block state - BlockState bs = block.getState(); - // Signs - if (bs instanceof org.bukkit.block.Sign sign) { - writeSign(block, bpBlock.getSignLines(), bpBlock.isGlowingText()); - } - // Chests, in general - 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(ih::setItem); - } - // Mob spawners - if (bs instanceof CreatureSpawner spawner) { - setSpawner(spawner, bpBlock.getCreatureSpawner()); - } - // Banners - if (bs instanceof Banner banner && bpBlock.getBannerPatterns() != null) { - bpBlock.getBannerPatterns().removeIf(Objects::isNull); - banner.setPatterns(bpBlock.getBannerPatterns()); - banner.update(true, false); - } - } - - private 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); - } - - /** - * Sets any entity that is in this location - * @param location - location - * @param list - list of entities to paste - */ - private void setEntity(Location location, List list) { - list.stream().filter(k -> k.getType() != null).forEach(k -> { - // Center, and just a bit high - Location center = location.add(new Vector(0.5, 0.5, 0.5)); - Util.getChunkAtAsync(center).thenRun(() -> { - LivingEntity e = (LivingEntity)location.getWorld().spawnEntity(center, 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) - Player owner = User.getInstance(island.getOwner()).getPlayer(); - if (owner != null) { - // Parse for the player's name first (in case placeholders might need it) - customName = customName.replace(TextVariables.NAME, owner.getName()); - // Now parse the placeholders - customName = plugin.getPlaceholdersManager().replacePlaceholders(owner, customName); - } - } - - // Actually set the custom name - e.setCustomName(customName); - } - k.configureEntity(e); - }); - }); - } - /** * Tracks the minimum and maximum block positions * @param l - location of block pasted @@ -397,50 +281,4 @@ public class BlueprintPaster { pos2.setZ(l.getBlockZ()); } } - - private void writeSign(final Block block, final List lines, boolean glow) { - BlockFace bf; - if (block.getType().name().contains("WALL_SIGN")) { - WallSign wallSign = (WallSign)block.getBlockData(); - bf = wallSign.getFacing(); - } else { - Sign sign = (Sign)block.getBlockData(); - bf = sign.getRotation(); - } - // Handle spawn sign - if (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 - org.bukkit.block.Sign s = (org.bukkit.block.Sign)block.getState(); - // 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(""); - if (island.getOwner() != null) { - for (int i = 0; i < 4; i++) { - s.setLine(i, Util.translateColorCodes(plugin.getLocalesManager().getOrDefault(User.getInstance(island.getOwner()), - addonName + ".sign.line" + i,"").replace(TextVariables.NAME, name))); - } - } - } else { - // Just paste - for (int i = 0; i < 4; i++) { - s.setLine(i, lines.get(i)); - } - } - s.setGlowingText(glow); - // Update the sign - s.update(); - } } diff --git a/src/main/java/world/bentobox/bentobox/nms/PasteHandler.java b/src/main/java/world/bentobox/bentobox/nms/PasteHandler.java new file mode 100644 index 000000000..33537aa7a --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/nms/PasteHandler.java @@ -0,0 +1,36 @@ +package world.bentobox.bentobox.nms; + +import org.bukkit.Location; +import org.bukkit.World; +import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock; +import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity; +import world.bentobox.bentobox.database.objects.Island; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * A helper class for {@link world.bentobox.bentobox.blueprints.BlueprintPaster} + */ +public interface PasteHandler { + /** + * Create a future to paste the blocks + * + * @param island the island + * @param world the world + * @param blockMap the block map + * @return the future + */ + CompletableFuture pasteBlocks(Island island, World world, Map blockMap); + + /** + * Create a future to paste the entities + * + * @param island the island + * @param world the world + * @param entityMap the entities map + * @return the future + */ + CompletableFuture pasteEntities(Island island, World world, Map> entityMap); +} diff --git a/src/main/java/world/bentobox/bentobox/nms/fallback/PasteHandlerImpl.java b/src/main/java/world/bentobox/bentobox/nms/fallback/PasteHandlerImpl.java new file mode 100644 index 000000000..64e6df247 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/nms/fallback/PasteHandlerImpl.java @@ -0,0 +1,27 @@ +package world.bentobox.bentobox.nms.fallback; + +import org.bukkit.Location; +import org.bukkit.World; +import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock; +import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.nms.PasteHandler; +import world.bentobox.bentobox.util.DefaultPasteUtil; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class PasteHandlerImpl implements PasteHandler { + @Override + public CompletableFuture pasteBlocks(Island island, World world, Map blockMap) { + blockMap.forEach((location, block) -> DefaultPasteUtil.setBlock(island, location, block)); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture pasteEntities(Island island, World world, Map> entityMap) { + entityMap.forEach((location, blueprintEntities) -> DefaultPasteUtil.setEntity(island, location, blueprintEntities)); + return CompletableFuture.completedFuture(null); + } +} diff --git a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java new file mode 100644 index 000000000..cf1e4db33 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java @@ -0,0 +1,237 @@ +package world.bentobox.bentobox.util; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.*; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.WallSign; +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.nms.PasteHandler; + +import java.util.*; + +/** + * 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(); + } + + /** + * Set the block to the location + * + * @param island - island + * @param location - location + * @param bpBlock - blueprint block + */ + public static void setBlock(Island island, Location location, BlueprintBlock bpBlock) { + 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 Bukkit.createBlockData(block.getBlockData()); + } catch (Exception 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) { + writeSign(island, block, bpBlock.getSignLines(), bpBlock.isGlowingText()); + } + // 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(ih::setItem); + } + // 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 void setEntity(Island island, Location location, List list) { + World world = location.getWorld(); + assert world != null; + Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null).forEach(k -> { + 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); + })); + } + + /** + * Write the lines to the sign at the block + * + * @param island - island + * @param block - block + * @param lines - lines + * @param glow - is sign glowing? + */ + public static void writeSign(Island island, final Block block, final List lines, boolean glow) { + 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 (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 + org.bukkit.block.Sign s = (org.bukkit.block.Sign) block.getState(); + // 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++) { + s.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++) { + s.setLine(i, lines.get(i)); + } + } + s.setGlowingText(glow); + // Update the sign + s.update(); + } +} diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java index c0a049119..e0b9cb8d2 100644 --- a/src/main/java/world/bentobox/bentobox/util/Util.java +++ b/src/main/java/world/bentobox/bentobox/util/Util.java @@ -47,8 +47,10 @@ import io.papermc.lib.PaperLib; import io.papermc.lib.features.blockstatesnapshot.BlockStateSnapshotResult; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.nms.PasteHandler; import world.bentobox.bentobox.nms.WorldRegenerator; + /** * A set of utility methods * @@ -64,6 +66,7 @@ public class Util { private static final String THE_END = "_the_end"; private static String serverVersion = null; private static BentoBox plugin = BentoBox.getInstance(); + private static PasteHandler pasteHandler = null; private static WorldRegenerator regenerator = null; private Util() {} @@ -722,6 +725,25 @@ public class Util { return regenerator; } + /** + * Set the paste handler the plugin will use + * @param pasteHandler the NMS paster + */ + public static void setPasteHandler(PasteHandler pasteHandler) { + Util.pasteHandler = pasteHandler; + } + + /** + * Get the paste handler the plugin will use + * @return an NMS accelerated class for this server + */ + public static PasteHandler getPasteHandler() { + if (pasteHandler == null) { + setPasteHandler(new world.bentobox.bentobox.nms.fallback.PasteHandlerImpl()); + } + return pasteHandler; + } + /** * Broadcast a localized message to all players with the permission {@link Server#BROADCAST_CHANNEL_USERS} * From 3ab7ac848453733f2871d7fa15c7299cde696b0a Mon Sep 17 00:00:00 2001 From: BONNe Date: Mon, 23 May 2022 20:46:02 +0300 Subject: [PATCH 32/74] Implement ability to create ItemStacks with Custom Model Data. (#1981) --- .../bentobox/bentobox/util/ItemParser.java | 56 ++++++++++++++++--- .../bentobox/util/ItemParserTest.java | 12 +++- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/util/ItemParser.java b/src/main/java/world/bentobox/bentobox/util/ItemParser.java index 7508312c4..8e59f0dd9 100644 --- a/src/main/java/world/bentobox/bentobox/util/ItemParser.java +++ b/src/main/java/world/bentobox/bentobox/util/ItemParser.java @@ -1,7 +1,9 @@ package world.bentobox.bentobox.util; import java.lang.reflect.Field; +import java.util.Arrays; import java.util.MissingFormatArgumentException; +import java.util.Optional; import java.util.UUID; import org.bukkit.Bukkit; @@ -56,38 +58,78 @@ public class ItemParser { return defaultItemStack; } + ItemStack returnValue = defaultItemStack; + String[] part = text.split(":"); try { + // Because I am lazy, and do not want to rewrite every parser, I will just add custom data as + // parameter and remove that array part form input data. + Optional first = Arrays.stream(part).filter(field -> field.matches("(CMD-[0-9]*)")).findFirst(); + Integer customModelData = null; + + if (first.isPresent()) { + // Ugly and fast way how to get rid of customData field. + String[] copyParts = new String[part.length - 1]; + int j = 0; + + for (String field : part) { + if (!field.matches("(CMD-[0-9]*)")) { + copyParts[j++] = field; + } + } + + // Replace original parts with the copy parts that does not have any CMD values. + part = copyParts; + // Now use value from Custom Data Model and parse it as integer. + customModelData = Integer.valueOf(first.get().replaceFirst("CMD-", "")); + } + // Check if there are more properties for the item stack if (part.length == 1) { // Parse material directly. It does not have any extra properties. - return new ItemStack(Material.valueOf(text.toUpperCase())); + returnValue = new ItemStack(Material.valueOf(text.toUpperCase())); } // Material-specific handling else if (part[0].contains("POTION") || part[0].equalsIgnoreCase("TIPPED_ARROW")) { // Parse Potions and Tipped Arrows - return parsePotion(part); + returnValue = parsePotion(part); } else if (part[0].contains("BANNER")) { // Parse Banners - return parseBanner(part); + returnValue = parseBanner(part); } else if (part[0].equalsIgnoreCase("PLAYER_HEAD")) { // Parse Player Heads - return parsePlayerHead(part); + returnValue = parsePlayerHead(part); } // Generic handling else if (part.length == 2) { // Material:Qty - return parseItemQuantity(part); + returnValue = parseItemQuantity(part); } else if (part.length == 3) { // Material:Durability:Qty - return parseItemDurabilityAndQuantity(part); + returnValue = parseItemDurabilityAndQuantity(part); + } + + if (returnValue != null) { + // If wrapper is just for code-style null-pointer checks. + if (customModelData != null) { + // We have custom data model. Now assign it to the item-stack. + ItemMeta itemMeta = returnValue.getItemMeta(); + + // Another null-pointer check for materials that does not have item meta. + if (itemMeta != null) { + itemMeta.setCustomModelData(customModelData); + // Update meta to the return item. + returnValue.setItemMeta(itemMeta); + } + } } } catch (Exception exception) { BentoBox.getInstance().logError("Could not parse item " + text + " " + exception.getLocalizedMessage()); + returnValue = defaultItemStack; } - return defaultItemStack; + return returnValue; } diff --git a/src/test/java/world/bentobox/bentobox/util/ItemParserTest.java b/src/test/java/world/bentobox/bentobox/util/ItemParserTest.java index 7ebecd6cd..2e8f7b4b3 100644 --- a/src/test/java/world/bentobox/bentobox/util/ItemParserTest.java +++ b/src/test/java/world/bentobox/bentobox/util/ItemParserTest.java @@ -1,7 +1,6 @@ package world.bentobox.bentobox.util; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -281,4 +280,13 @@ public class ItemParserTest { assertEquals(defaultItem, ItemParser.parse("STNE:AA:5", defaultItem)); assertEquals(defaultItem, ItemParser.parse("WOODEN_SWORD:4:AA", defaultItem)); } + + + @Test + public void parseCustomModelData() { + ItemStack result = ItemParser.parse("WOODEN_SWORD:CMD-23151212:2"); + assertEquals(Material.WOODEN_SWORD, result.getType()); + assertEquals(2, result.getAmount()); + assertNull(ItemParser.parse("WOODEN_SWORD:CMD-23151212:2:CMD-23151212")); + } } From 345b9e25641f5eefe1ea0b4933e881399cb87719 Mon Sep 17 00:00:00 2001 From: BONNe Date: Mon, 23 May 2022 23:23:22 +0300 Subject: [PATCH 33/74] Fixes an incorrect :CMD- parsing The issue was that it used an incorrect value for parsing Material. Instead of stripped part[0] it used whole text with CMD in it. --- src/main/java/world/bentobox/bentobox/util/ItemParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/util/ItemParser.java b/src/main/java/world/bentobox/bentobox/util/ItemParser.java index 8e59f0dd9..aaf583e9f 100644 --- a/src/main/java/world/bentobox/bentobox/util/ItemParser.java +++ b/src/main/java/world/bentobox/bentobox/util/ItemParser.java @@ -88,7 +88,7 @@ public class ItemParser { // Check if there are more properties for the item stack if (part.length == 1) { // Parse material directly. It does not have any extra properties. - returnValue = new ItemStack(Material.valueOf(text.toUpperCase())); + returnValue = new ItemStack(Material.valueOf(part[0].toUpperCase())); } // Material-specific handling else if (part[0].contains("POTION") || part[0].equalsIgnoreCase("TIPPED_ARROW")) { From aad10ad74f90620b230d1398f1debe403d0c4bd6 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sun, 29 May 2022 22:56:50 +0300 Subject: [PATCH 34/74] Move database dependencies to the Spigot Libraries. (#1982) --- pom.xml | 2 ++ src/main/resources/plugin.yml | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index c4be8f5b6..937c2c761 100644 --- a/pom.xml +++ b/pom.xml @@ -230,11 +230,13 @@ org.mongodb mongodb-driver ${mongodb.version} + provided postgresql postgresql 9.1-901-1.jdbc4 + provided diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 26b0e64cc..f6af7553e 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -28,6 +28,11 @@ softdepend: - HolographicDisplays - EconomyPlus +libraries: + - mysql:mysql-connector-java:8.0.27 + - org.mongodb:mongodb-driver:${mongodb.version} + - postgresql:postgresql:9.1-901-1.jdbc4 + permissions: bentobox.admin: description: Allows admin command usage From 6f2a9929c08ae480e48937570dea9aefb33af635 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sun, 5 Jun 2022 21:52:37 +0300 Subject: [PATCH 35/74] Fixes Glowing Item frames protection (#1985) * Fixes glowing item frame protection. Glowing item frames were not protected. Fixes #475 * Fixes check-style. --- .../listeners/flags/protection/BlockInteractionListener.java | 1 + .../listeners/flags/protection/PlaceBlocksListener.java | 2 +- .../flags/protection/BlockInteractionListenerTest.java | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java index 55deac57e..154fcfee3 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java @@ -198,6 +198,7 @@ public class BlockInteractionListener extends FlagListener { case END_PORTAL_FRAME: checkIsland(e, player, loc, Flags.PLACE_BLOCKS); break; + case GLOW_ITEM_FRAME: case ITEM_FRAME: checkIsland(e, player, loc, Flags.ITEM_FRAME); break; diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PlaceBlocksListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PlaceBlocksListener.java index 703e11bcd..1ca9758e9 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PlaceBlocksListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PlaceBlocksListener.java @@ -52,7 +52,7 @@ public class PlaceBlocksListener extends FlagListener { */ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerHitItemFrame(PlayerInteractEntityEvent e) { - if (e.getRightClicked().getType().equals(EntityType.ITEM_FRAME)) { + if (e.getRightClicked().getType().equals(EntityType.ITEM_FRAME) || e.getRightClicked().getType().equals(EntityType.GLOW_ITEM_FRAME)) { if (!checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.PLACE_BLOCKS)) return; checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.ITEM_FRAME); } diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java index 8c722f717..fb2bd2e9f 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java @@ -110,6 +110,7 @@ public class BlockInteractionListenerTest extends AbstractCommonSetup { clickedBlocks.put(Material.DRAGON_EGG, Flags.DRAGON_EGG); clickedBlocks.put(Material.END_PORTAL_FRAME, Flags.PLACE_BLOCKS); clickedBlocks.put(Material.ITEM_FRAME, Flags.ITEM_FRAME); + clickedBlocks.put(Material.GLOW_ITEM_FRAME, Flags.ITEM_FRAME); clickedBlocks.put(Material.SWEET_BERRY_BUSH, Flags.BREAK_BLOCKS); clickedBlocks.put(Material.CAKE, Flags.CAKE); clickedBlocks.put(Material.BEEHIVE, Flags.HIVE); From 85b52f4bfb46e220618fddb52b09aa6558fe4d28 Mon Sep 17 00:00:00 2001 From: BONNe Date: Tue, 14 Jun 2022 00:38:53 +0300 Subject: [PATCH 36/74] Implement new 1.19 materials and entities (#1990) * Update to Spigot and Paper API 1.19 and Java 17 * Set minimal version to 1.18. * Add breeding protection for Axolotl and Goats (1.18) Add breeding protection for Frog and Allay (1.19) * Clean up switch statement in BlockInteractionListener * Add chest boat support to BreakBlocksListener. Part of #1987 * Add powdered snow pickup with bucket protection. * Add glow ink sac protection on signs. Switch to SheepDyeWoolEvent as it now contains player variable. * Clean up Hurting Listener * Mark 1.16.5, 1.17 and 1.17.1 as incompatible. Mark 1.18, 1.18.1 and 1.18.2 as supported. Add 1.19 as compatible. Part of 1987 * Add ChestBoat inventory protection. Part of #1987 * Clean up LockAndBanListener * Add MANGROVE_PRESSURE_PLATE protection. Part of #1987 * Add Glow Item Frame protection to player interact event. * Clean up MobSpawnListener * Clean up ChestDamageListener * Change from custom method to Tag.PRESSURE_PLATES to detect pressure plates. Part of #1987 * Implement proper chest boat protection. Part of #1987 * Move to 1.19 R1 world regenerator. Part of #1987 * Add allay to the animal entity list. Part of #1987 * Add axolotl and other fish scooping protection. Part of #1987 * Fixes Bucket and Glass Bottle filling. Buckets and bottles were not working since cauldron splitting by type. This change fixes that. It also protects from filling bottles with water from water sources or waterlogged blocks. * Remove 1.17.1 compatibility check for biome adapter. Add cheezee 1.19.1 compatibility version. * Fixes chest boat interactions. Part of #1987 * Implement Allay protection. New flag ALLAY is required to interact with allays. Part of #1987 * Prevent visitors for being targeted by entities if ENTITY_ATTACK is enabled. * Implement Sculk Sensor and Shrieker activation protection setting. Part of #1987 * Add music discs to the LangUtilsHook Part of #1987 * Fixes failing unit-tests. --- pom.xml | 10 +- .../json/BentoboxTypeAdapterFactory.java | 2 +- .../bentobox/hooks/LangUtilsHook.java | 2 + .../protection/BlockInteractionListener.java | 376 ++++++++++-------- .../flags/protection/BreakBlocksListener.java | 48 ++- .../flags/protection/BreedingListener.java | 29 +- .../flags/protection/BucketListener.java | 35 +- .../flags/protection/DyeListener.java | 37 +- .../protection/EntityInteractListener.java | 82 ++-- .../flags/protection/HurtingListener.java | 29 +- .../flags/protection/InventoryListener.java | 148 +++++-- .../flags/protection/LockAndBanListener.java | 55 +-- .../PhysicalInteractionListener.java | 61 ++- .../flags/protection/PlaceBlocksListener.java | 123 +++--- .../flags/protection/SculkSensorListener.java | 53 +++ .../protection/SculkShriekerListener.java | 53 +++ .../flags/settings/MobSpawnListener.java | 116 +++--- .../worldsettings/ChestDamageListener.java | 14 +- .../InvincibleVisitorsListener.java | 25 ++ .../world/bentobox/bentobox/lists/Flags.java | 65 +-- .../WorldRegeneratorImpl.java | 6 +- .../world/bentobox/bentobox/util/Util.java | 28 +- .../versions/ServerCompatibility.java | 20 +- src/main/resources/locales/en-US.yml | 26 ++ src/main/resources/plugin.yml | 2 +- .../clicklisteners/GeoMobLimitTabTest.java | 4 +- .../BlockInteractionListenerTest.java | 5 +- .../flags/settings/MobSpawnListenerTest.java | 6 + .../ChestDamageListenerTest.java | 20 +- .../bentobox/managers/FlagsManagerTest.java | 2 +- 30 files changed, 955 insertions(+), 527 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/listeners/flags/protection/SculkSensorListener.java create mode 100644 src/main/java/world/bentobox/bentobox/listeners/flags/protection/SculkShriekerListener.java rename src/main/java/world/bentobox/bentobox/nms/{v1_18_R2 => v1_19_R1}/WorldRegeneratorImpl.java (87%) diff --git a/pom.xml b/pom.xml index 937c2c761..ef331e098 100644 --- a/pom.xml +++ b/pom.xml @@ -63,15 +63,15 @@ UTF-8 UTF-8 - 16 + 17 2.0.9 3.12.8 - 1.18.2-R0.1-SNAPSHOT + 1.19-R0.1-SNAPSHOT - 1.16.5-R0.1-SNAPSHOT + 1.19-R0.1-SNAPSHOT 2.2.1 1.7 2.10.9 @@ -164,7 +164,7 @@ papermc - https://papermc.io/repo/repository/maven-public/ + https://repo.papermc.io/repository/maven-public/ - com.destroystokyo.paper + io.papermc.paper paper-api ${paper.version} provided diff --git a/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java b/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java index b9582485e..147ffe5f2 100644 --- a/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java +++ b/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java @@ -56,7 +56,7 @@ public class BentoboxTypeAdapterFactory implements TypeAdapterFactory { if (Location.class.isAssignableFrom(rawType)) { // Use our current location adapter for backward compatibility return (TypeAdapter) new LocationTypeAdapter(); - } else if (Biome.class.isAssignableFrom(rawType) && !ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_17_1)) { // TODO: Any better way ? + } else if (Biome.class.isAssignableFrom(rawType)) { return (TypeAdapter) new BiomeTypeAdapter(); } else if (Enum.class.isAssignableFrom(rawType)) { return new EnumTypeAdapter(rawType); diff --git a/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java b/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java index b58864e2f..45629ffe8 100644 --- a/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java +++ b/src/main/java/world/bentobox/bentobox/hooks/LangUtilsHook.java @@ -626,6 +626,8 @@ public class LangUtilsHook extends Hook { case MUSIC_DISC_11 -> "C418 - 11"; case MUSIC_DISC_WAIT -> "C418 - wait"; case MUSIC_DISC_PIGSTEP -> "Lena Raine - Pigstep"; + case MUSIC_DISC_5 -> "Samuel Åberg - 5"; + case MUSIC_DISC_OTHERSIDE -> "Lena Raine - otherside"; default -> null; }; } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java index 154fcfee3..5f87ea232 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java @@ -1,12 +1,15 @@ package world.bentobox.bentobox.listeners.flags.protection; +import java.util.Collections; import java.util.Map; import java.util.Optional; +import org.bukkit.FluidCollisionMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Tag; import org.bukkit.block.Block; +import org.bukkit.block.data.Waterlogged; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.EventHandler; @@ -26,236 +29,269 @@ import world.bentobox.bentobox.lists.Flags; * Handle interaction with blocks * @author tastybento */ -public class BlockInteractionListener extends FlagListener { +public class BlockInteractionListener extends FlagListener +{ /** - * These cover materials in another server version. - * This avoids run time errors due to unknown enum values, at the expense of a string comparison + * These cover materials in another server version. This avoids run time errors due to unknown enum values, at the + * expense of a string comparison */ private final static Map stringFlags; - static { - stringFlags = Map.of("RESPAWN_ANCHOR", "PLACE_BLOCKS"); + + static + { + stringFlags = Map.of( + "ACACIA_CHEST_BOAT", "CHEST", + "BIRCH_CHEST_BOAT", "CHEST", + "JUNGLE_CHEST_BOAT", "CHEST", + "DARK_OAK_CHEST_BOAT", "CHEST", + "MANGROVE_CHEST_BOAT", "CHEST", + "OAK_CHEST_BOAT", "CHEST", + "SPRUCE_CHEST_BOAT", "CHEST"); } /** * Handle interaction with blocks + * * @param e - event */ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerInteract(final PlayerInteractEvent e) { + public void onPlayerInteract(final PlayerInteractEvent e) + { // We only care about the RIGHT_CLICK_BLOCK action. - if (!e.getAction().equals(Action.RIGHT_CLICK_BLOCK) || e.getClickedBlock() == null) { + if (!e.getAction().equals(Action.RIGHT_CLICK_BLOCK) || e.getClickedBlock() == null) + { return; } // Check clicked block - checkClickedBlock(e, e.getPlayer(), e.getClickedBlock().getLocation(), e.getClickedBlock().getType()); + this.checkClickedBlock(e, e.getPlayer(), e.getClickedBlock().getLocation(), e.getClickedBlock().getType()); // Now check for in-hand items - if (e.getItem() != null && !e.getItem().getType().equals(Material.AIR)) { + if (e.getItem() != null && !e.getItem().getType().equals(Material.AIR)) + { // Boats - if (e.getItem().getType().name().endsWith("_BOAT")) { - checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.BOAT); + if (e.getItem().getType().name().endsWith("BOAT")) + { + this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.BOAT); } - // Spawn eggs - else if (e.getItem().getType().name().endsWith("_SPAWN_EGG")) { - checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.SPAWN_EGGS); + else if (e.getItem().getType().name().endsWith("_SPAWN_EGG")) + { + this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.SPAWN_EGGS); } - // Now check for in-hand items - if (e.getItem() != null) { - if (e.getItem().getType().name().contains("BOAT")) { - checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.PLACE_BLOCKS); - return; - } - switch (e.getItem().getType()) { - case ENDER_PEARL: - checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.ENDER_PEARL); - break; - case BONE_MEAL: - checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.PLACE_BLOCKS); - break; - default: - break; + else if (e.getItem().getType() == Material.ENDER_PEARL) + { + this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.ENDER_PEARL); + } + else if (e.getItem().getType() == Material.BONE_MEAL) + { + this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.PLACE_BLOCKS); + } + else if (e.getItem().getType() == Material.GLASS_BOTTLE) + { + Block targetedBlock = e.getPlayer().getTargetBlockExact(5, FluidCollisionMode.ALWAYS); + + // Check if player is clicking on water or waterlogged block with a bottle. + if (targetedBlock != null && (Material.WATER.equals(targetedBlock.getType()) || + targetedBlock.getBlockData() instanceof Waterlogged)) + { + this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.BREWING); } } } } + /** * Check if an action can occur on a clicked block + * * @param e - event called * @param player - player * @param loc - location of clicked block * @param type - material type of clicked block */ - private void checkClickedBlock(Event e, Player player, Location loc, Material type) { + private void checkClickedBlock(Event e, Player player, Location loc, Material type) + { // Handle pots - if (type.name().startsWith("POTTED")) { - checkIsland(e, player, loc, Flags.FLOWER_POT); - return; - } - if (Tag.ANVIL.isTagged(type)) { - checkIsland(e, player, loc, Flags.ANVIL); - return; - } - if (Tag.BUTTONS.isTagged(type)) { - checkIsland(e, player, loc, Flags.BUTTON); - return; - } - if (Tag.BEDS.isTagged(type)) { - checkIsland(e, player, loc, Flags.BED); - return; - } - if (Tag.DOORS.isTagged(type)) { - checkIsland(e, player, loc, Flags.DOOR); - return; - } - if (Tag.SHULKER_BOXES.isTagged(type)) { - checkIsland(e, player, loc, Flags.SHULKER_BOX); - return; - } - if (Tag.TRAPDOORS.isTagged(type)) { - checkIsland(e, player, loc, Flags.TRAPDOOR); + if (type.name().startsWith("POTTED")) + { + this.checkIsland(e, player, loc, Flags.FLOWER_POT); return; } - switch (type) { - case BEACON: - checkIsland(e, player, loc, Flags.BEACON); - break; - case BREWING_STAND: - case CAULDRON: - checkIsland(e, player, loc, Flags.BREWING); - break; - case BEEHIVE: - case BEE_NEST: - checkIsland(e, player, loc, Flags.HIVE); - break; - case BARREL: - checkIsland(e, player, loc, Flags.BARREL); - break; - case CHEST: - case CHEST_MINECART: - checkIsland(e, player, loc, Flags.CHEST); - break; - case TRAPPED_CHEST: - checkIsland(e, player, loc, Flags.TRAPPED_CHEST); - break; - case FLOWER_POT: - checkIsland(e, player, loc, Flags.FLOWER_POT); - break; - case COMPOSTER: - checkIsland(e, player, loc, Flags.COMPOSTER); - break; - case DISPENSER: - checkIsland(e, player, loc, Flags.DISPENSER); - break; - case DROPPER: - checkIsland(e, player, loc, Flags.DROPPER); - break; - case HOPPER: - case HOPPER_MINECART: - checkIsland(e, player, loc, Flags.HOPPER); - break; - case BLAST_FURNACE: - case CAMPFIRE: - case FURNACE_MINECART: - case FURNACE: - case SMOKER: - checkIsland(e, player, loc, Flags.FURNACE); - break; - case ENCHANTING_TABLE: - checkIsland(e, player, loc, Flags.ENCHANTING); - break; - case ENDER_CHEST: - checkIsland(e, player, loc, Flags.ENDER_CHEST); - break; - case JUKEBOX: - checkIsland(e, player, loc, Flags.JUKEBOX); - break; - case NOTE_BLOCK: - checkIsland(e, player, loc, Flags.NOTE_BLOCK); - break; - case CRAFTING_TABLE: - case CARTOGRAPHY_TABLE: - case GRINDSTONE: - case STONECUTTER: - case LOOM: - checkIsland(e, player, loc, Flags.CRAFTING); - break; - case LEVER: - checkIsland(e, player, loc, Flags.LEVER); - break; - case REDSTONE_WIRE: - case REPEATER: - case COMPARATOR: - case DAYLIGHT_DETECTOR: - checkIsland(e, player, loc, Flags.REDSTONE); - break; - case DRAGON_EGG: - checkIsland(e, player, loc, Flags.DRAGON_EGG); - break; - case END_PORTAL_FRAME: - checkIsland(e, player, loc, Flags.PLACE_BLOCKS); - break; - case GLOW_ITEM_FRAME: - case ITEM_FRAME: - checkIsland(e, player, loc, Flags.ITEM_FRAME); - break; - case SWEET_BERRY_BUSH: - checkIsland(e, player, loc, Flags.BREAK_BLOCKS); - break; - case CAKE: - checkIsland(e, player, loc, Flags.CAKE); - break; - case OAK_FENCE_GATE: - case SPRUCE_FENCE_GATE: - case BIRCH_FENCE_GATE: - case JUNGLE_FENCE_GATE: - case DARK_OAK_FENCE_GATE: - case ACACIA_FENCE_GATE: - case CRIMSON_FENCE_GATE: - case WARPED_FENCE_GATE: - checkIsland(e, player, loc, Flags.GATE); - break; - default: - if (stringFlags.containsKey(type.name())) { - Optional f = BentoBox.getInstance().getFlagsManager().getFlag(stringFlags.get(type.name())); - f.ifPresent(flag -> checkIsland(e, player, loc, flag)); + if (Tag.ANVIL.isTagged(type)) + { + this.checkIsland(e, player, loc, Flags.ANVIL); + return; + } + + if (Tag.BUTTONS.isTagged(type)) + { + this.checkIsland(e, player, loc, Flags.BUTTON); + return; + } + + if (Tag.BEDS.isTagged(type)) + { + this.checkIsland(e, player, loc, Flags.BED); + return; + } + + if (Tag.DOORS.isTagged(type)) + { + this.checkIsland(e, player, loc, Flags.DOOR); + return; + } + + if (Tag.SHULKER_BOXES.isTagged(type)) + { + this.checkIsland(e, player, loc, Flags.SHULKER_BOX); + return; + } + + if (Tag.TRAPDOORS.isTagged(type)) + { + this.checkIsland(e, player, loc, Flags.TRAPDOOR); + return; + } + + if (Tag.FENCE_GATES.isTagged(type)) + { + this.checkIsland(e, player, loc, Flags.GATE); + } + // TODO: 1.18 compatibility + // if (Tag.ITEMS_CHEST_BOATS.isTagged(type)) { + // this.checkIsland(e, player, loc, Flags.CHEST); + // } + + switch (type) + { + case BEACON -> this.checkIsland(e, player, loc, Flags.BEACON); + case BREWING_STAND -> this.checkIsland(e, player, loc, Flags.BREWING); + case BEEHIVE, BEE_NEST -> this.checkIsland(e, player, loc, Flags.HIVE); + case BARREL -> this.checkIsland(e, player, loc, Flags.BARREL); + case CHEST, CHEST_MINECART -> this.checkIsland(e, player, loc, Flags.CHEST); + case TRAPPED_CHEST -> this.checkIsland(e, player, loc, Flags.TRAPPED_CHEST); + case FLOWER_POT -> this.checkIsland(e, player, loc, Flags.FLOWER_POT); + case COMPOSTER -> this.checkIsland(e, player, loc, Flags.COMPOSTER); + case DISPENSER -> this.checkIsland(e, player, loc, Flags.DISPENSER); + case DROPPER -> this.checkIsland(e, player, loc, Flags.DROPPER); + case HOPPER, HOPPER_MINECART -> this.checkIsland(e, player, loc, Flags.HOPPER); + case BLAST_FURNACE, CAMPFIRE, FURNACE_MINECART, FURNACE, SMOKER -> + this.checkIsland(e, player, loc, Flags.FURNACE); + case ENCHANTING_TABLE -> this.checkIsland(e, player, loc, Flags.ENCHANTING); + case ENDER_CHEST -> this.checkIsland(e, player, loc, Flags.ENDER_CHEST); + case JUKEBOX -> this.checkIsland(e, player, loc, Flags.JUKEBOX); + case NOTE_BLOCK -> this.checkIsland(e, player, loc, Flags.NOTE_BLOCK); + case CRAFTING_TABLE, CARTOGRAPHY_TABLE, GRINDSTONE, STONECUTTER, LOOM -> + this.checkIsland(e, player, loc, Flags.CRAFTING); + case LEVER -> this.checkIsland(e, player, loc, Flags.LEVER); + case REDSTONE_WIRE, REPEATER, COMPARATOR, DAYLIGHT_DETECTOR -> this.checkIsland(e, player, loc, Flags.REDSTONE); + case DRAGON_EGG -> this.checkIsland(e, player, loc, Flags.DRAGON_EGG); + case END_PORTAL_FRAME, RESPAWN_ANCHOR -> this.checkIsland(e, player, loc, Flags.PLACE_BLOCKS); + case GLOW_ITEM_FRAME, ITEM_FRAME -> this.checkIsland(e, player, loc, Flags.ITEM_FRAME); + case SWEET_BERRY_BUSH -> this.checkIsland(e, player, loc, Flags.BREAK_BLOCKS); + case CAKE -> this.checkIsland(e, player, loc, Flags.CAKE); + case LAVA_CAULDRON -> + { + if (BlockInteractionListener.holds(player, Material.BUCKET)) + { + this.checkIsland(e, player, loc, Flags.COLLECT_LAVA); + } + } + case WATER_CAULDRON -> + { + if (BlockInteractionListener.holds(player, Material.BUCKET)) + { + this.checkIsland(e, player, loc, Flags.COLLECT_WATER); + } + else if (BlockInteractionListener.holds(player, Material.GLASS_BOTTLE) || + BlockInteractionListener.holds(player, Material.POTION)) + { + this.checkIsland(e, player, loc, Flags.BREWING); + } + } + case POWDER_SNOW_CAULDRON -> + { + if (BlockInteractionListener.holds(player, Material.BUCKET)) + { + this.checkIsland(e, player, loc, Flags.COLLECT_POWDERED_SNOW); + } + } + case CAULDRON -> + { + if (BlockInteractionListener.holds(player, Material.WATER_BUCKET) || + BlockInteractionListener.holds(player, Material.LAVA_BUCKET) || + BlockInteractionListener.holds(player, Material.POWDER_SNOW_BUCKET)) + { + this.checkIsland(e, player, loc, Flags.BUCKET); + } + else if (BlockInteractionListener.holds(player, Material.POTION)) + { + this.checkIsland(e, player, loc, Flags.BREWING); + } + } + default -> + { + if (stringFlags.containsKey(type.name())) + { + Optional f = BentoBox.getInstance().getFlagsManager().getFlag(stringFlags.get(type.name())); + f.ifPresent(flag -> this.checkIsland(e, player, loc, flag)); + } } } } + /** - * When breaking blocks is allowed, this protects - * specific blocks from being broken, which would bypass the protection. - * For example, player enables break blocks, but chests are still protected - * Fires after the BreakBlocks check. + * When breaking blocks is allowed, this protects specific blocks from being broken, which would bypass the + * protection. For example, player enables break blocks, but chests are still protected Fires after the BreakBlocks + * check. * * @param e - event */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onBlockBreak(final BlockBreakEvent e) { - checkClickedBlock(e, e.getPlayer(), e.getBlock().getLocation(), e.getBlock().getType()); + public void onBlockBreak(final BlockBreakEvent e) + { + this.checkClickedBlock(e, e.getPlayer(), e.getBlock().getLocation(), e.getBlock().getType()); } + /** * Prevents dragon eggs from flying out of an island's protected space + * * @param e - event */ @EventHandler(priority = EventPriority.LOWEST) - public void onDragonEggTeleport(BlockFromToEvent e) { + public void onDragonEggTeleport(BlockFromToEvent e) + { Block block = e.getBlock(); - if (!block.getType().equals(Material.DRAGON_EGG) || !getIWM().inWorld(block.getLocation())) { + + if (!block.getType().equals(Material.DRAGON_EGG) || !this.getIWM().inWorld(block.getLocation())) + { return; } + // If egg starts in a protected island... // Cancel if toIsland is not fromIsland or if there is no protected island there // This protects against eggs dropping into adjacent islands, e.g. island distance and protection range are equal - Optional fromIsland = getIslands().getProtectedIslandAt(block.getLocation()); - Optional toIsland = getIslands().getProtectedIslandAt(e.getToBlock().getLocation()); + Optional fromIsland = this.getIslands().getProtectedIslandAt(block.getLocation()); + Optional toIsland = this.getIslands().getProtectedIslandAt(e.getToBlock().getLocation()); fromIsland.ifPresent(from -> e.setCancelled(toIsland.map(to -> to != from).orElse(true))); } -} + + + /** + * This method returns if player is holding given material in main or offhand. + * @param player Player that must be checked. + * @param material item that mus t be checjed. + * @return {@code true} if player is holding item in main hand or offhand. + */ + private static boolean holds(Player player, Material material) + { + return player.getInventory().getItemInMainHand().getType().equals(material) || + player.getInventory().getItemInOffHand().getType().equals(material); + } +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java index 6defb0ef6..5bbd83bce 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java @@ -58,19 +58,23 @@ public class BreakBlocksListener extends FlagListener { * @param e - event */ @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onPlayerInteract(final PlayerInteractEvent e) { + public void onPlayerInteract(final PlayerInteractEvent e) + { // Only handle hitting things - if (!e.getAction().equals(Action.LEFT_CLICK_BLOCK)) { + if (!e.getAction().equals(Action.LEFT_CLICK_BLOCK) || e.getClickedBlock() == null) + { return; } + Player p = e.getPlayer(); Location l = e.getClickedBlock().getLocation(); - switch (e.getClickedBlock().getType()) { - case CAKE -> checkIsland(e, p, l, Flags.BREAK_BLOCKS); - case SPAWNER -> checkIsland(e, p, l, Flags.BREAK_SPAWNERS); - case DRAGON_EGG -> checkIsland(e, p, l, Flags.DRAGON_EGG); - case HOPPER -> checkIsland(e, p, l, Flags.BREAK_HOPPERS); - default -> {} + + switch (e.getClickedBlock().getType()) + { + case CAKE -> this.checkIsland(e, p, l, Flags.BREAK_BLOCKS); + case SPAWNER -> this.checkIsland(e, p, l, Flags.BREAK_SPAWNERS); + case DRAGON_EGG -> this.checkIsland(e, p, l, Flags.DRAGON_EGG); + case HOPPER -> this.checkIsland(e, p, l, Flags.BREAK_HOPPERS); } } @@ -79,16 +83,26 @@ public class BreakBlocksListener extends FlagListener { * @param e - event */ @EventHandler(priority = EventPriority.LOW, ignoreCancelled=true) - public void onVehicleDamageEvent(VehicleDamageEvent e) { + public void onVehicleDamageEvent(VehicleDamageEvent e) + { Location l = e.getVehicle().getLocation(); - if (getIWM().inWorld(l) && e.getAttacker() instanceof Player p) { - String vehicleType = e.getVehicle().getType().toString(); - if (e.getVehicle().getType().equals(EntityType.BOAT)) { - checkIsland(e, p, l, Flags.BOAT); - } else if (vehicleType.contains("MINECART")) { - checkIsland(e, p, l, Flags.MINECART); - } else { - checkIsland(e, p, l, Flags.BREAK_BLOCKS); + + if (getIWM().inWorld(l) && e.getAttacker() instanceof Player p) + { + String vehicleType = e.getVehicle().getType().name(); + + // 1.19 introduced Chest Boat. + if (vehicleType.contains("BOAT")) + { + this.checkIsland(e, p, l, Flags.BOAT); + } + else if (vehicleType.contains("MINECART")) + { + this.checkIsland(e, p, l, Flags.MINECART); + } + else + { + this.checkIsland(e, p, l, Flags.BREAK_BLOCKS); } } } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreedingListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreedingListener.java index 1548535b2..63cdfe68e 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreedingListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreedingListener.java @@ -56,16 +56,27 @@ public class BreedingListener extends FlagListener { bi.put(EntityType.TURTLE, Collections.singletonList(Material.SEAGRASS)); bi.put(EntityType.PANDA, Collections.singletonList(Material.BAMBOO)); bi.put(EntityType.FOX, Collections.singletonList(Material.SWEET_BERRIES)); - if (Enums.getIfPresent(EntityType.class, "BEES").isPresent()) { // 1.15.2 - bi.put(EntityType.BEE, Arrays.asList(Material.SUNFLOWER, Material.ORANGE_TULIP, Material.PINK_TULIP, - Material.RED_TULIP, Material.WHITE_TULIP, Material.ALLIUM, - Material.AZURE_BLUET, Material.BLUE_ORCHID, Material.CORNFLOWER, - Material.DANDELION, Material.OXEYE_DAISY, Material.PEONY, Material.POPPY)); - } - if (Enums.getIfPresent(EntityType.class, "HOGLIN").isPresent()) { - bi.put(EntityType.HOGLIN, Collections.singletonList(Material.CRIMSON_FUNGUS)); // 1.16.1 - bi.put(EntityType.STRIDER, Collections.singletonList(Material.WARPED_FUNGUS)); // 1.16.1 + // 1.15+ + bi.put(EntityType.BEE, Arrays.asList(Material.SUNFLOWER, Material.ORANGE_TULIP, Material.PINK_TULIP, + Material.RED_TULIP, Material.WHITE_TULIP, Material.ALLIUM, + Material.AZURE_BLUET, Material.BLUE_ORCHID, Material.CORNFLOWER, + Material.DANDELION, Material.OXEYE_DAISY, Material.PEONY, Material.POPPY)); + // 1.16+ + bi.put(EntityType.HOGLIN, Collections.singletonList(Material.CRIMSON_FUNGUS)); + bi.put(EntityType.STRIDER, Collections.singletonList(Material.WARPED_FUNGUS)); + // 1.18+ + bi.put(EntityType.AXOLOTL, Collections.singletonList(Material.TROPICAL_FISH_BUCKET)); + bi.put(EntityType.GOAT, Collections.singletonList(Material.WHEAT)); + // 1.19+ + // TODO: remove one 1.18 is dropped. + if (Enums.getIfPresent(EntityType.class, "FROG").isPresent()) { + bi.put(EntityType.FROG, Collections.singletonList(Material.SLIME_BALL)); + bi.put(EntityType.ALLAY, Collections.singletonList(Material.AMETHYST_SHARD)); } + // Helper + // if (Enums.getIfPresent(EntityType.class, "").isPresent()) { + // bi.put(EntityType., Collections.singletonList(Material.)); + // } BREEDING_ITEMS = Collections.unmodifiableMap(bi); } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BucketListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BucketListener.java index 3f411267f..2a23c0ba1 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BucketListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BucketListener.java @@ -3,9 +3,10 @@ package world.bentobox.bentobox.listeners.flags.protection; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.entity.Axolotl; +import org.bukkit.entity.Fish; import org.bukkit.entity.MushroomCow; import org.bukkit.entity.Player; -import org.bukkit.entity.TropicalFish; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.player.PlayerBucketEmptyEvent; @@ -31,7 +32,7 @@ public class BucketListener extends FlagListener { public void onBucketEmpty(final PlayerBucketEmptyEvent e) { // This is where the water or lava actually will be dumped Block dumpBlock = e.getBlockClicked().getRelative(e.getBlockFace()); - checkIsland(e, e.getPlayer(), dumpBlock.getLocation(), Flags.BUCKET); + this.checkIsland(e, e.getPlayer(), dumpBlock.getLocation(), Flags.BUCKET); } /** @@ -42,19 +43,35 @@ public class BucketListener extends FlagListener { public void onBucketFill(final PlayerBucketFillEvent e) { Player p = e.getPlayer(); Location l = e.getBlockClicked().getLocation(); + + if (e.getItemStack() == null) + { + // Null-pointer check. + return; + } + // Check filling of various liquids - switch (e.getItemStack().getType()) { - case LAVA_BUCKET -> checkIsland(e, p, l, Flags.COLLECT_LAVA); - case WATER_BUCKET -> checkIsland(e, p, l, Flags.COLLECT_WATER); - case MILK_BUCKET -> checkIsland(e, p, l, Flags.MILKING); - default -> checkIsland(e, p, l, Flags.BUCKET); + switch (e.getItemStack().getType()) + { + case LAVA_BUCKET -> this.checkIsland(e, p, l, Flags.COLLECT_LAVA); + case WATER_BUCKET -> this.checkIsland(e, p, l, Flags.COLLECT_WATER); + case POWDER_SNOW_BUCKET -> this.checkIsland(e, p, l, Flags.COLLECT_POWDERED_SNOW); + case MILK_BUCKET -> this.checkIsland(e, p, l, Flags.MILKING); + default -> this.checkIsland(e, p, l, Flags.BUCKET); } } @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onTropicalFishScooping(final PlayerInteractEntityEvent e) { - if (e.getRightClicked() instanceof TropicalFish && e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.WATER_BUCKET)) { - checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.FISH_SCOOPING); + if (e.getRightClicked() instanceof Fish && + e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.WATER_BUCKET)) + { + this.checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.FISH_SCOOPING); + } + else if (e.getRightClicked() instanceof Axolotl && + e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.WATER_BUCKET)) + { + this.checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.AXOLOTL_SCOOPING); } } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/DyeListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/DyeListener.java index 4e2b4658e..327e2598a 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/DyeListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/DyeListener.java @@ -1,10 +1,10 @@ package world.bentobox.bentobox.listeners.flags.protection; -import org.bukkit.entity.EntityType; +import org.bukkit.Material; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.entity.SheepDyeWoolEvent; import org.bukkit.event.player.PlayerInteractEvent; import world.bentobox.bentobox.api.flags.FlagListener; @@ -22,24 +22,35 @@ public class DyeListener extends FlagListener { * @param e - event */ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerInteract(final PlayerInteractEvent e) { - if (e.getClickedBlock() == null || e.getItem() == null) { + public void onPlayerInteract(final PlayerInteractEvent e) + { + if (e.getClickedBlock() == null || e.getItem() == null) + { return; } - if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK) && e.getClickedBlock().getType().name().contains("SIGN") - && e.getItem().getType().name().contains("DYE")) { - checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.DYE); + if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK) && + e.getClickedBlock().getType().name().contains("SIGN") && + (e.getItem().getType().name().contains("DYE") || e.getItem().getType().equals(Material.GLOW_INK_SAC))) + { + this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.DYE); } } + + /** + * Prevents from interacting with sheep. + * @param e - event + */ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerInteract(final PlayerInteractEntityEvent e) { - // We cannot use SheepDyeWoolEvent since it doesn't provide who dyed the sheep - if (e.getRightClicked().getType().equals(EntityType.SHEEP) - && (e.getPlayer().getInventory().getItemInMainHand().getType().name().contains("DYE") - || e.getPlayer().getInventory().getItemInOffHand().getType().name().contains("DYE"))) { - checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.DYE); + public void onPlayerInteract(final SheepDyeWoolEvent e) + { + if (e.getPlayer() == null) + { + // Sheep is not dyed by the player. + return; } + + this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.DYE); } } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListener.java index 3f273b5c3..2cb6d255e 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListener.java @@ -2,13 +2,7 @@ package world.bentobox.bentobox.listeners.flags.protection; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.entity.Animals; -import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.Boat; -import org.bukkit.entity.Player; -import org.bukkit.entity.Vehicle; -import org.bukkit.entity.Villager; -import org.bukkit.entity.WanderingTrader; +import org.bukkit.entity.*; import org.bukkit.entity.minecart.RideableMinecart; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -17,6 +11,8 @@ import org.bukkit.event.player.PlayerInteractEntityEvent; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; +import world.bentobox.bentobox.versions.ServerCompatibility; + /** * Handles interaction with entities like armor stands @@ -34,34 +30,68 @@ public class EntityInteractListener extends FlagListener { } @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onPlayerInteractEntity(PlayerInteractEntityEvent e) { + public void onPlayerInteractEntity(PlayerInteractEntityEvent e) + { Player p = e.getPlayer(); Location l = e.getRightClicked().getLocation(); - if (e.getRightClicked() instanceof Vehicle) { - // Animal riding - if (e.getRightClicked() instanceof Animals) { - checkIsland(e, p, l, Flags.RIDING); + + if (e.getRightClicked() instanceof Vehicle) + { + if (e.getRightClicked() instanceof Animals) + { + // Animal riding + this.checkIsland(e, p, l, Flags.RIDING); } - // Minecart riding - else if (e.getRightClicked() instanceof RideableMinecart) { - checkIsland(e, p, l, Flags.MINECART); + else if (e.getRightClicked() instanceof RideableMinecart) + { + // Minecart riding + this.checkIsland(e, p, l, Flags.MINECART); } - // Boat riding - else if (e.getRightClicked() instanceof Boat) { - checkIsland(e, p, l, Flags.BOAT); + else if (!ServerCompatibility.getInstance().isVersion( + ServerCompatibility.ServerVersion.V1_18, + ServerCompatibility.ServerVersion.V1_18_1, + ServerCompatibility.ServerVersion.V1_18_2) && + e.getPlayer().isSneaking() && e.getRightClicked() instanceof ChestBoat) + { + // Access to chest boat since 1.19 + this.checkIsland(e, p, l, Flags.CHEST); + } + else if (e.getRightClicked() instanceof Boat) + { + // Boat riding + this.checkIsland(e, p, l, Flags.BOAT); } } - // Villager trading - else if (e.getRightClicked() instanceof Villager || e.getRightClicked() instanceof WanderingTrader) { + else if (e.getRightClicked() instanceof Villager || e.getRightClicked() instanceof WanderingTrader) + { + // Villager trading // Check naming and check trading - checkIsland(e, p, l, Flags.TRADING); - if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG)) { - checkIsland(e, p, l, Flags.NAME_TAG); + this.checkIsland(e, p, l, Flags.TRADING); + + if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG)) + { + this.checkIsland(e, p, l, Flags.NAME_TAG); } } - // Name tags - else if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG)) { - checkIsland(e, p, l, Flags.NAME_TAG); + else if (!ServerCompatibility.getInstance().isVersion( + ServerCompatibility.ServerVersion.V1_18, + ServerCompatibility.ServerVersion.V1_18_1, + ServerCompatibility.ServerVersion.V1_18_2) && + e.getRightClicked() instanceof Allay) + { + // Allay item giving/taking + this.checkIsland(e, p, l, Flags.ALLAY); + + // Check naming + if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG)) + { + this.checkIsland(e, p, l, Flags.NAME_TAG); + } + } + else if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG)) + { + // Name tags + this.checkIsland(e, p, l, Flags.NAME_TAG); } } } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java index 887c278ef..32d18f0ba 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/HurtingListener.java @@ -14,7 +14,6 @@ import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Parrot; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; -import org.bukkit.entity.Villager; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.entity.EntityDamageByEntityEvent; @@ -31,7 +30,6 @@ import world.bentobox.bentobox.api.flags.Flag; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; import world.bentobox.bentobox.util.Util; -import world.bentobox.bentobox.versions.ServerCompatibility; /** @@ -50,17 +48,24 @@ public class HurtingListener extends FlagListener { * @param e - event */ @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onEntityDamage(final EntityDamageByEntityEvent e) { + public void onEntityDamage(final EntityDamageByEntityEvent e) + { // Mobs being hurt - if (Util.isPassiveEntity(e.getEntity())) { - respond(e, e.getDamager(), Flags.HURT_ANIMALS); - } else if (e.getEntity() instanceof Villager || e.getEntityType().name().equals("WANDERING_TRADER")) { // TODO: Simplify when 1.13.2 support is dropped - respond(e, e.getDamager(), Flags.HURT_VILLAGERS); - } else if (Util.isHostileEntity(e.getEntity())) { - respond(e, e.getDamager(), Flags.HURT_MONSTERS); + if (Util.isPassiveEntity(e.getEntity())) + { + this.respond(e, e.getDamager(), Flags.HURT_ANIMALS); + } + else if (e.getEntity() instanceof AbstractVillager) + { + this.respond(e, e.getDamager(), Flags.HURT_VILLAGERS); + } + else if (Util.isHostileEntity(e.getEntity())) + { + this.respond(e, e.getDamager(), Flags.HURT_MONSTERS); } } + /** * Finds the true attacker, even if the attack was via a projectile * @param e - event @@ -159,12 +164,6 @@ public class HurtingListener extends FlagListener { */ @EventHandler(priority = EventPriority.LOW, ignoreCancelled=true) public void onLingeringPotionSplash(final LingeringPotionSplashEvent e) { - // TODO Switch this to 1.13 when we move to 1.14 officially - if (!ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_14, ServerCompatibility.ServerVersion.V1_14_1)) { - // We're disabling this check for non-1.14 servers. - return; - } - // Try to get the shooter Projectile projectile = e.getEntity(); if (projectile.getShooter() instanceof Player) { diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java index 5f408b2a3..74a60e5b9 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java @@ -13,6 +13,7 @@ import org.bukkit.block.Furnace; import org.bukkit.block.Hopper; import org.bukkit.block.ShulkerBox; import org.bukkit.entity.Animals; +import org.bukkit.entity.ChestBoat; import org.bukkit.entity.NPC; import org.bukkit.entity.Player; import org.bukkit.entity.minecart.HopperMinecart; @@ -20,83 +21,146 @@ import org.bukkit.entity.minecart.StorageMinecart; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.inventory.InventoryHolder; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; +import world.bentobox.bentobox.versions.ServerCompatibility; + /** * Handles inventory protection * @author tastybento */ -public class InventoryListener extends FlagListener { +public class InventoryListener extends FlagListener +{ + /** + * Prevents players opening inventories + * @param event - event + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled=true) + public void onInventoryOpen(InventoryOpenEvent event) + { + InventoryHolder inventoryHolder = event.getInventory().getHolder(); + + if (inventoryHolder == null || !(event.getPlayer() instanceof Player player)) + { + return; + } + + if (inventoryHolder instanceof Animals) + { + // Prevent opening animal inventories. + this.checkIsland(event, player, event.getInventory().getLocation(), Flags.MOUNT_INVENTORY); + } + else if (!ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18, + ServerCompatibility.ServerVersion.V1_18_1, + ServerCompatibility.ServerVersion.V1_18_2) && + inventoryHolder instanceof ChestBoat) + { + // Prevent opening chest inventories + this.checkIsland(event, player, event.getInventory().getLocation(), Flags.CHEST); + } + } + /** * Prevents players picking items from inventories * @param e - event */ @EventHandler(priority = EventPriority.LOW, ignoreCancelled=true) - public void onInventoryClick(InventoryClickEvent e) { - Player player = (Player)e.getWhoClicked(); - + public void onInventoryClick(InventoryClickEvent e) + { + Player player = (Player) e.getWhoClicked(); InventoryHolder inventoryHolder = e.getInventory().getHolder(); - if (inventoryHolder == null || !(e.getWhoClicked() instanceof Player)) { + + if (inventoryHolder == null || !(e.getWhoClicked() instanceof Player)) + { return; } - if (inventoryHolder instanceof Animals) { - checkIsland(e, player, e.getInventory().getLocation(), Flags.MOUNT_INVENTORY); + + if (inventoryHolder instanceof Animals) + { + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.MOUNT_INVENTORY); } - else if (inventoryHolder instanceof Dispenser) { - checkIsland(e, player, e.getInventory().getLocation(), Flags.DISPENSER); + else if (inventoryHolder instanceof Dispenser) + { + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.DISPENSER); } - else if (inventoryHolder instanceof Dropper) { - checkIsland(e, player, e.getInventory().getLocation(), Flags.DROPPER); + else if (inventoryHolder instanceof Dropper) + { + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.DROPPER); } - else if (inventoryHolder instanceof Hopper - || inventoryHolder instanceof HopperMinecart) { - checkIsland(e, player, e.getInventory().getLocation(), Flags.HOPPER); + else if (inventoryHolder instanceof Hopper || inventoryHolder instanceof HopperMinecart) + { + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.HOPPER); } - else if (inventoryHolder instanceof Furnace) { - checkIsland(e, player, e.getInventory().getLocation(), Flags.FURNACE); + else if (inventoryHolder instanceof Furnace) + { + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.FURNACE); } - else if (inventoryHolder instanceof BrewingStand) { - checkIsland(e, player, e.getInventory().getLocation(), Flags.BREWING); + else if (inventoryHolder instanceof BrewingStand) + { + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.BREWING); } - else if (inventoryHolder instanceof Beacon) { - checkIsland(e, player, e.getInventory().getLocation(), Flags.BEACON); + else if (inventoryHolder instanceof Beacon) + { + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.BEACON); } - else if (inventoryHolder instanceof NPC) { - checkIsland(e, player, e.getInventory().getLocation(), Flags.TRADING); + else if (inventoryHolder instanceof NPC) + { + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.TRADING); } - else if (inventoryHolder instanceof Barrel) { - checkIsland(e, player, e.getInventory().getLocation(), Flags.BARREL); + else if (inventoryHolder instanceof Barrel) + { + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.BARREL); } - else if (inventoryHolder instanceof ShulkerBox) { - checkIsland(e, player, e.getInventory().getLocation(), Flags.SHULKER_BOX); + else if (inventoryHolder instanceof ShulkerBox) + { + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.SHULKER_BOX); } - else if (inventoryHolder instanceof Chest c) { - checkInvHolder(c.getLocation(), e, player); + else if (inventoryHolder instanceof Chest c) + { + this.checkInvHolder(c.getLocation(), e, player); } - else if (inventoryHolder instanceof DoubleChest dc) { - checkInvHolder(dc.getLocation(), e, player); + else if (inventoryHolder instanceof DoubleChest dc) + { + this.checkInvHolder(dc.getLocation(), e, player); } - else if (inventoryHolder instanceof StorageMinecart) { - checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST); + else if (inventoryHolder instanceof StorageMinecart) + { + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST); } - else if (!(inventoryHolder instanceof Player)) { + else if (!ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18, ServerCompatibility.ServerVersion.V1_18_1, ServerCompatibility.ServerVersion.V1_18_2) && + inventoryHolder instanceof ChestBoat) + { + // TODO: 1.19 added chest boat. Remove compatibility check when 1.18 is dropped. + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST); + } + else if (!(inventoryHolder instanceof Player)) + { // All other containers - checkIsland(e, player, e.getInventory().getLocation(), Flags.CONTAINER); + this.checkIsland(e, player, e.getInventory().getLocation(), Flags.CONTAINER); } } - private void checkInvHolder(Location l, InventoryClickEvent e, Player player) { - if (l.getBlock().getType().equals(Material.TRAPPED_CHEST)) { - checkIsland(e, player, l, Flags.TRAPPED_CHEST); - } else { - checkIsland(e, player, l, Flags.CHEST); + + /** + * This method runs check based on clicked chest type. + * @param l location of chest. + * @param e click event. + * @param player player who clicked. + */ + private void checkInvHolder(Location l, InventoryClickEvent e, Player player) + { + if (l.getBlock().getType().equals(Material.TRAPPED_CHEST)) + { + this.checkIsland(e, player, l, Flags.TRAPPED_CHEST); + } + else + { + this.checkIsland(e, player, l, Flags.CHEST); } - - } } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java index 37ec8be1e..d980debe9 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/LockAndBanListener.java @@ -104,22 +104,31 @@ public class LockAndBanListener extends FlagListener { * @param loc - location to check * @return CheckResult LOCKED, BANNED or OPEN. If an island is locked, that will take priority over banned */ - private CheckResult check(@NonNull Player player, Location loc) { + private CheckResult check(@NonNull Player player, Location loc) + { // Ops or NPC's are allowed everywhere - if (player.isOp() || player.hasMetadata("NPC")) { + if (player.isOp() || player.hasMetadata("NPC")) + { return CheckResult.OPEN; } + // See if the island is locked to non-members or player is banned - return getIslands().getProtectedIslandAt(loc) - .map(is -> { - if (is.isBanned(player.getUniqueId())) { - return player.hasPermission(getIWM().getPermissionPrefix(loc.getWorld()) + "mod.bypassban") ? CheckResult.OPEN : CheckResult.BANNED; - } - if (!is.isAllowed(User.getInstance(player), Flags.LOCK)) { - return player.hasPermission(getIWM().getPermissionPrefix(loc.getWorld()) + "mod.bypasslock") ? CheckResult.OPEN : CheckResult.LOCKED; - } - return CheckResult.OPEN; - }).orElse(CheckResult.OPEN); + return this.getIslands().getProtectedIslandAt(loc). + map(is -> + { + if (is.isBanned(player.getUniqueId())) + { + return player.hasPermission(getIWM().getPermissionPrefix(loc.getWorld()) + "mod.bypassban") ? + CheckResult.OPEN : CheckResult.BANNED; + } + if (!is.isAllowed(User.getInstance(player), Flags.LOCK)) + { + return player.hasPermission(getIWM().getPermissionPrefix(loc.getWorld()) + "mod.bypasslock") ? + CheckResult.OPEN : CheckResult.LOCKED; + } + return CheckResult.OPEN; + }). + orElse(CheckResult.OPEN); } /** @@ -128,19 +137,17 @@ public class LockAndBanListener extends FlagListener { * @param loc - location to check * @return true if banned */ - private CheckResult checkAndNotify(@NonNull Player player, Location loc) { - CheckResult r = check(player,loc); - switch (r) { - case BANNED: - User.getInstance(player).notify("commands.island.ban.you-are-banned"); - break; - case LOCKED: - User.getInstance(player).notify("protection.locked"); - break; - default: - break; + private CheckResult checkAndNotify(@NonNull Player player, Location loc) + { + CheckResult result = this.check(player, loc); + + switch (result) + { + case BANNED -> User.getInstance(player).notify("commands.island.ban.you-are-banned"); + case LOCKED -> User.getInstance(player).notify("protection.locked"); } - return r; + + return result; } /** diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListener.java index 26ef9d7d8..6a04aa8fa 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PhysicalInteractionListener.java @@ -13,67 +13,66 @@ import org.bukkit.event.player.PlayerInteractEvent; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; + /** * @author tastybento * */ -public class PhysicalInteractionListener extends FlagListener { - +public class PhysicalInteractionListener extends FlagListener +{ /** * Handle physical interaction with blocks * Crop trample, pressure plates, triggering redstone, tripwires * @param e - event */ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerInteract(PlayerInteractEvent e) { - if (!e.getAction().equals(Action.PHYSICAL)) { + public void onPlayerInteract(PlayerInteractEvent e) + { + if (!e.getAction().equals(Action.PHYSICAL) || e.getClickedBlock() == null) + { return; } - if (isPressurePlate(e.getClickedBlock().getType())) { + + if (Tag.PRESSURE_PLATES.isTagged(e.getClickedBlock().getType())) + { // Pressure plates - checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.PRESSURE_PLATE); + this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.PRESSURE_PLATE); return; } - switch (e.getClickedBlock().getType()) { - case FARMLAND: - // Crop trample - checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.CROP_TRAMPLE); - break; - case TURTLE_EGG: - checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.TURTLE_EGGS); - break; - default: - break; + + switch (e.getClickedBlock().getType()) + { + case FARMLAND -> this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.CROP_TRAMPLE); + case TURTLE_EGG -> this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.TURTLE_EGGS); } } + /** * Protects buttons and plates from being activated by projectiles * @param e - event */ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onProjectileHit(EntityInteractEvent e) { - if (!(e.getEntity() instanceof Projectile p)) { + public void onProjectileHit(EntityInteractEvent e) + { + if (!(e.getEntity() instanceof Projectile p)) + { return; } - if (p.getShooter() instanceof Player) { - if (Tag.WOODEN_BUTTONS.isTagged(e.getBlock().getType())) { - checkIsland(e, (Player)p.getShooter(), e.getBlock().getLocation(), Flags.BUTTON); + + if (p.getShooter() instanceof Player) + { + if (Tag.WOODEN_BUTTONS.isTagged(e.getBlock().getType())) + { + this.checkIsland(e, (Player) p.getShooter(), e.getBlock().getLocation(), Flags.BUTTON); return; } - if (isPressurePlate(e.getBlock().getType())) { + if (Tag.PRESSURE_PLATES.isTagged(e.getBlock().getType())) + { // Pressure plates - checkIsland(e, (Player)p.getShooter(), e.getBlock().getLocation(), Flags.PRESSURE_PLATE); + this.checkIsland(e, (Player) p.getShooter(), e.getBlock().getLocation(), Flags.PRESSURE_PLATE); } } } - - private boolean isPressurePlate(Material material) { - return switch (material) { - case STONE_PRESSURE_PLATE, POLISHED_BLACKSTONE_PRESSURE_PLATE, ACACIA_PRESSURE_PLATE, BIRCH_PRESSURE_PLATE, CRIMSON_PRESSURE_PLATE, DARK_OAK_PRESSURE_PLATE, HEAVY_WEIGHTED_PRESSURE_PLATE, JUNGLE_PRESSURE_PLATE, LIGHT_WEIGHTED_PRESSURE_PLATE, OAK_PRESSURE_PLATE, SPRUCE_PRESSURE_PLATE, WARPED_PRESSURE_PLATE -> true; - default -> false; - }; - } - } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PlaceBlocksListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PlaceBlocksListener.java index 1ca9758e9..4e5d06b13 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PlaceBlocksListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/PlaceBlocksListener.java @@ -18,95 +18,126 @@ import world.bentobox.bentobox.lists.Flags; /** * @author tastybento */ -public class PlaceBlocksListener extends FlagListener { - +public class PlaceBlocksListener extends FlagListener +{ /** * Check blocks being placed in general * * @param e - event */ @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onBlockPlace(final BlockPlaceEvent e) { - if (e.getBlock().getType().equals(Material.FIRE) - || e.getItemInHand() == null // Note that this should never happen officially, but it's possible for other plugins to cause it to happen - || e.getItemInHand().getType().equals(Material.WRITABLE_BOOK) - || e.getItemInHand().getType().equals(Material.WRITTEN_BOOK)) { + public void onBlockPlace(final BlockPlaceEvent e) + { + if (e.getBlock().getType().equals(Material.FIRE) || + e.getItemInHand() == null || // Note that this should never happen officially, but it's possible for other plugins to cause it to happen + e.getItemInHand().getType().equals(Material.WRITABLE_BOOK) || + e.getItemInHand().getType().equals(Material.WRITTEN_BOOK)) + { // Books can only be placed on lecterns and as such are protected by the LECTERN flag. return; } - checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.PLACE_BLOCKS); + + this.checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.PLACE_BLOCKS); } + /** * Check for paintings and other hanging placements + * * @param e - event */ @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onHangingPlace(final HangingPlaceEvent e) { - checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.PLACE_BLOCKS); + public void onHangingPlace(final HangingPlaceEvent e) + { + this.checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.PLACE_BLOCKS); } + /** * Handles placing items into ItemFrames + * * @param e - event */ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerHitItemFrame(PlayerInteractEntityEvent e) { - if (e.getRightClicked().getType().equals(EntityType.ITEM_FRAME) || e.getRightClicked().getType().equals(EntityType.GLOW_ITEM_FRAME)) { - if (!checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.PLACE_BLOCKS)) return; - checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.ITEM_FRAME); + public void onPlayerHitItemFrame(PlayerInteractEntityEvent e) + { + if (e.getRightClicked().getType().equals(EntityType.ITEM_FRAME) || + e.getRightClicked().getType().equals(EntityType.GLOW_ITEM_FRAME)) + { + if (!this.checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.PLACE_BLOCKS)) + { + return; + } + + this.checkIsland(e, e.getPlayer(), e.getRightClicked().getLocation(), Flags.ITEM_FRAME); } } + /** - * Handle placing of fireworks, item frames, mine carts, end crystals, chests and boats on land - * The doors and chests are related to an exploit. + * Handle placing of fireworks, item frames, mine carts, end crystals, chests and boats on land The doors and chests + * are related to an exploit. + * * @param e - event */ @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onPlayerInteract(final PlayerInteractEvent e) { - if (!e.getAction().equals(Action.RIGHT_CLICK_BLOCK) || e.getClickedBlock() == null) { + public void onPlayerInteract(final PlayerInteractEvent e) + { + if (!e.getAction().equals(Action.RIGHT_CLICK_BLOCK) || e.getClickedBlock() == null) + { return; } - switch (e.getClickedBlock().getType()) { - case FIREWORK_ROCKET: - checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.PLACE_BLOCKS); - return; - case RAIL: - case POWERED_RAIL: - case DETECTOR_RAIL: - case ACTIVATOR_RAIL: - if ((e.getMaterial() == Material.MINECART || e.getMaterial() == Material.CHEST_MINECART || e.getMaterial() == Material.HOPPER_MINECART - || e.getMaterial() == Material.TNT_MINECART || e.getMaterial() == Material.FURNACE_MINECART)) { - checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.MINECART); + switch (e.getClickedBlock().getType()) + { + case FIREWORK_ROCKET -> + { + this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.PLACE_BLOCKS); } - return; - default: - // Check in-hand items - if (e.getMaterial().equals(Material.FIREWORK_ROCKET) - || e.getMaterial().equals(Material.ARMOR_STAND) - || e.getMaterial().equals(Material.END_CRYSTAL) - || e.getMaterial().equals(Material.ITEM_FRAME) - //|| Tag.DOORS.isTagged(e.getMaterial()) - || e.getMaterial().equals(Material.CHEST) - || e.getMaterial().equals(Material.TRAPPED_CHEST)) { - checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.PLACE_BLOCKS); + case RAIL, POWERED_RAIL, DETECTOR_RAIL, ACTIVATOR_RAIL -> + { + if (e.getMaterial() == Material.MINECART || + e.getMaterial() == Material.CHEST_MINECART || + e.getMaterial() == Material.HOPPER_MINECART || + e.getMaterial() == Material.TNT_MINECART || + e.getMaterial() == Material.FURNACE_MINECART) + { + this.checkIsland(e, e.getPlayer(), e.getClickedBlock().getLocation(), Flags.MINECART); + } } - else if (e.getMaterial().name().contains("BOAT")) { - checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.BOAT); + default -> + { + // Check in-hand items + if (e.getMaterial() == Material.FIREWORK_ROCKET || + e.getMaterial() == Material.ARMOR_STAND || + e.getMaterial() == Material.END_CRYSTAL || + e.getMaterial() == Material.ITEM_FRAME || + e.getMaterial() == Material.GLOW_ITEM_FRAME || + e.getMaterial() == Material.CHEST || + e.getMaterial() == Material.TRAPPED_CHEST) + { + this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.PLACE_BLOCKS); + } + else if (e.getMaterial().name().contains("BOAT")) + { + this.checkIsland(e, e.getPlayer(), e.getPlayer().getLocation(), Flags.BOAT); + } } } } + /** * Handles Frost Walking on visitor's islands. This creates ice blocks, which is like placing blocks + * * @param e - event */ - @EventHandler(priority = EventPriority.LOW, ignoreCancelled=true) - public void onBlockForm(EntityBlockFormEvent e) { - if (e.getNewState().getType().equals(Material.FROSTED_ICE) && e.getEntity() instanceof Player) { - checkIsland(e, (Player)e.getEntity(), e.getBlock().getLocation(), Flags.FROST_WALKER); + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onBlockForm(EntityBlockFormEvent e) + { + if (e.getNewState().getType().equals(Material.FROSTED_ICE) && e.getEntity() instanceof Player) + { + this.checkIsland(e, (Player) e.getEntity(), e.getBlock().getLocation(), Flags.FROST_WALKER); } } } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/SculkSensorListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/SculkSensorListener.java new file mode 100644 index 000000000..3f4541200 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/SculkSensorListener.java @@ -0,0 +1,53 @@ +// +// Created by BONNe +// Copyright - 2022 +// + + +package world.bentobox.bentobox.listeners.flags.protection; + + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockReceiveGameEvent; + +import world.bentobox.bentobox.api.flags.FlagListener; +import world.bentobox.bentobox.lists.Flags; +import world.bentobox.bentobox.versions.ServerCompatibility; + + +/** + * This method prevents sculk sensor from activation based on protection settings. + */ +public class SculkSensorListener extends FlagListener +{ + /** + * This listener detects if a visitor activates sculk sensor, and block it, if required. + * @param event Sculk activation event. + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onSculkSensor(BlockReceiveGameEvent event) + { + if (!this.getIWM().inWorld(event.getBlock().getWorld())) + { + return; + } + + if (ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18, + ServerCompatibility.ServerVersion.V1_18_1, + ServerCompatibility.ServerVersion.V1_18_2)) + { + // TODO: 1.18 compatibility exit + return; + } + + if (event.getBlock().getType() == Material.SCULK_SENSOR && + event.getEntity() != null && + event.getEntity() instanceof Player player) + { + this.checkIsland(event, player, event.getBlock().getLocation(), Flags.SCULK_SENSOR, true); + } + } +} diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/SculkShriekerListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/SculkShriekerListener.java new file mode 100644 index 000000000..a31ca75be --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/SculkShriekerListener.java @@ -0,0 +1,53 @@ +// +// Created by BONNe +// Copyright - 2022 +// + + +package world.bentobox.bentobox.listeners.flags.protection; + + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockReceiveGameEvent; + +import world.bentobox.bentobox.api.flags.FlagListener; +import world.bentobox.bentobox.lists.Flags; +import world.bentobox.bentobox.versions.ServerCompatibility; + + +/** + * This method prevents sculk shrieker from activation based on protection settings. + */ +public class SculkShriekerListener extends FlagListener +{ + /** + * This listener detects if a visitor activates sculk sensor, and block it, if required. + * @param event Sculk activation event. + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onSculkShrieker(BlockReceiveGameEvent event) + { + if (!this.getIWM().inWorld(event.getBlock().getWorld())) + { + return; + } + + if (ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18, + ServerCompatibility.ServerVersion.V1_18_1, + ServerCompatibility.ServerVersion.V1_18_2)) + { + // TODO: 1.18 compatibility exit + return; + } + + if (event.getBlock().getType() == Material.SCULK_SHRIEKER && + event.getEntity() != null && + event.getEntity() instanceof Player player) + { + this.checkIsland(event, player, event.getBlock().getLocation(), Flags.SCULK_SHRIEKER, true); + } + } +} diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListener.java index ffa634028..d30cab11e 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListener.java @@ -19,68 +19,82 @@ import world.bentobox.bentobox.util.Util; * Handles natural mob spawning. * @author tastybento */ -public class MobSpawnListener extends FlagListener { +public class MobSpawnListener extends FlagListener +{ + /** + * Prevents mobs spawning naturally + * + * @param e - event + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onMobSpawnEvent(CreatureSpawnEvent e) + { + this.onMobSpawn(e); + } + /** * Prevents mobs spawning naturally - * * @param e - event */ - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onMobSpawnEvent(CreatureSpawnEvent e) { - onMobSpawn(e); - } - /** - * Prevents mobs spawning naturally - * - * @param e - event - * @return true if cancelled - */ - @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - void onMobSpawn(CreatureSpawnEvent e) { + void onMobSpawn(CreatureSpawnEvent e) + { // If not in the right world, or spawning is not natural return - if (!getIWM().inWorld(e.getEntity().getLocation())) { + if (!this.getIWM().inWorld(e.getEntity().getLocation())) + { return; } - switch (e.getSpawnReason()) { - // Natural - case DEFAULT: - case DROWNED: - case JOCKEY: - case LIGHTNING: - case MOUNT: - case NATURAL: - case NETHER_PORTAL: - case OCELOT_BABY: - case PATROL: - case RAID: - case REINFORCEMENTS: - case SILVERFISH_BLOCK: - //case SLIME_SPLIT: messes with slimes from spawners, slime must have previously existed to create another - case TRAP: - case VILLAGE_DEFENSE: - case VILLAGE_INVASION: - boolean cancelNatural = shouldCancel(e.getEntity(), e.getLocation(), Flags.ANIMAL_NATURAL_SPAWN, Flags.MONSTER_NATURAL_SPAWN); - e.setCancelled(cancelNatural); - return; + + switch (e.getSpawnReason()) + { + // Natural + case DEFAULT, DROWNED, JOCKEY, LIGHTNING, MOUNT, NATURAL, NETHER_PORTAL, OCELOT_BABY, PATROL, + RAID, REINFORCEMENTS, SILVERFISH_BLOCK, TRAP, VILLAGE_DEFENSE, VILLAGE_INVASION -> + { + boolean cancelNatural = this.shouldCancel(e.getEntity(), + e.getLocation(), + Flags.ANIMAL_NATURAL_SPAWN, + Flags.MONSTER_NATURAL_SPAWN); + e.setCancelled(cancelNatural); + } // Spawners - case SPAWNER: - boolean cancelSpawners = shouldCancel(e.getEntity(), e.getLocation(), Flags.ANIMAL_SPAWNERS_SPAWN, Flags.MONSTER_SPAWNERS_SPAWN); - e.setCancelled(cancelSpawners); - return; - default: - return; + case SPAWNER -> + { + boolean cancelSpawners = this.shouldCancel(e.getEntity(), + e.getLocation(), + Flags.ANIMAL_SPAWNERS_SPAWN, + Flags.MONSTER_SPAWNERS_SPAWN); + e.setCancelled(cancelSpawners); + } } } - private boolean shouldCancel(Entity entity, Location loc, Flag animalSpawnFlag, Flag monsterSpawnFlag) { + + /** + * This method checks if entity should be cancelled from spawning in given location base on flag values. + * @param entity Entity that is checked. + * @param loc location where entity is spawned. + * @param animalSpawnFlag Animal Spawn Flag. + * @param monsterSpawnFlag Monster Spawn Flag. + * @return {@code true} if flag prevents entity to spawn, {@code false} otherwise. + */ + private boolean shouldCancel(Entity entity, Location loc, Flag animalSpawnFlag, Flag monsterSpawnFlag) + { Optional island = getIslands().getIslandAt(loc); - if (Util.isHostileEntity(entity) && !(entity instanceof PufferFish)) { - return island.map(i -> !i.isAllowed(monsterSpawnFlag)).orElseGet(() -> !monsterSpawnFlag.isSetForWorld(entity.getWorld())); - } else if (Util.isPassiveEntity(entity) || entity instanceof PufferFish) { - return island.map(i -> !i.isAllowed(animalSpawnFlag)).orElseGet(() -> !animalSpawnFlag.isSetForWorld(entity.getWorld())); - } - return false; - } -} + if (Util.isHostileEntity(entity) && !(entity instanceof PufferFish)) + { + return island.map(i -> !i.isAllowed(monsterSpawnFlag)). + orElseGet(() -> !monsterSpawnFlag.isSetForWorld(entity.getWorld())); + } + else if (Util.isPassiveEntity(entity) || entity instanceof PufferFish) + { + return island.map(i -> !i.isAllowed(animalSpawnFlag)). + orElseGet(() -> !animalSpawnFlag.isSetForWorld(entity.getWorld())); + } + else + { + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ChestDamageListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ChestDamageListener.java index 8192740b9..6547bfe79 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ChestDamageListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/ChestDamageListener.java @@ -1,6 +1,7 @@ package world.bentobox.bentobox.listeners.flags.worldsettings; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.entity.EntityExplodeEvent; @@ -18,12 +19,13 @@ public class ChestDamageListener extends FlagListener { * @param e - event */ @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) - public void onExplosion(final EntityExplodeEvent e) { - if (getIWM().inWorld(e.getLocation()) && !Flags.CHEST_DAMAGE.isSetForWorld(e.getLocation().getWorld())) { - e.blockList().removeIf(b -> b.getType().equals(Material.CHEST) - || b.getType().equals(Material.TRAPPED_CHEST) - || b.getType().name().contains("SHULKER_BOX") - ); + public void onExplosion(final EntityExplodeEvent e) + { + if (getIWM().inWorld(e.getLocation()) && !Flags.CHEST_DAMAGE.isSetForWorld(e.getLocation().getWorld())) + { + e.blockList().removeIf(b -> b.getType().equals(Material.CHEST) || + b.getType().equals(Material.TRAPPED_CHEST) || + Tag.SHULKER_BOXES.isTagged(b.getType())); } } } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListener.java index 140eceecd..b13000a70 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListener.java @@ -12,6 +12,8 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.event.entity.EntityTargetLivingEntityEvent; import org.bukkit.event.inventory.ClickType; import world.bentobox.bentobox.BentoBox; @@ -157,5 +159,28 @@ public class InvincibleVisitorsListener extends FlagListener implements ClickHan } + /** + * This listener cancels entity targeting a player if he is a visitor, and visitors are immune to entity damage. + * @param e EntityTargetLivingEntityEvent + */ + @EventHandler(priority = EventPriority.HIGHEST) + public void onVisitorTargeting(EntityTargetLivingEntityEvent e) + { + World world = e.getEntity().getWorld(); + + if (!(e.getTarget() instanceof Player p) || + !this.getIWM().inWorld(world) || + e.getTarget().hasMetadata("NPC") || + this.getIslands().userIsOnIsland(world, User.getInstance(e.getEntity())) || + this.PVPAllowed(p.getLocation()) || + e.getReason() == EntityTargetEvent.TargetReason.TARGET_DIED || + !this.getIWM().getIvSettings(world).contains(DamageCause.ENTITY_ATTACK.name())) + { + return; + } + + // Cancel targeting event. + e.setCancelled(true); + } } diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index 8546409d8..53d631335 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -1,5 +1,6 @@ package world.bentobox.bentobox.lists; +import com.google.common.base.Enums; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -13,30 +14,7 @@ import world.bentobox.bentobox.api.flags.clicklisteners.CycleClick; import world.bentobox.bentobox.listeners.flags.clicklisteners.CommandRankClickListener; import world.bentobox.bentobox.listeners.flags.clicklisteners.GeoLimitClickListener; import world.bentobox.bentobox.listeners.flags.clicklisteners.MobLimitClickListener; -import world.bentobox.bentobox.listeners.flags.protection.BlockInteractionListener; -import world.bentobox.bentobox.listeners.flags.protection.BreakBlocksListener; -import world.bentobox.bentobox.listeners.flags.protection.BreedingListener; -import world.bentobox.bentobox.listeners.flags.protection.BucketListener; -import world.bentobox.bentobox.listeners.flags.protection.DyeListener; -import world.bentobox.bentobox.listeners.flags.protection.EggListener; -import world.bentobox.bentobox.listeners.flags.protection.ElytraListener; -import world.bentobox.bentobox.listeners.flags.protection.EntityInteractListener; -import world.bentobox.bentobox.listeners.flags.protection.ExperiencePickupListener; -import world.bentobox.bentobox.listeners.flags.protection.FireListener; -import world.bentobox.bentobox.listeners.flags.protection.HurtingListener; -import world.bentobox.bentobox.listeners.flags.protection.InventoryListener; -import world.bentobox.bentobox.listeners.flags.protection.ItemDropPickUpListener; -import world.bentobox.bentobox.listeners.flags.protection.LeashListener; -import world.bentobox.bentobox.listeners.flags.protection.LecternListener; -import world.bentobox.bentobox.listeners.flags.protection.LockAndBanListener; -import world.bentobox.bentobox.listeners.flags.protection.PaperExperiencePickupListener; -import world.bentobox.bentobox.listeners.flags.protection.PhysicalInteractionListener; -import world.bentobox.bentobox.listeners.flags.protection.PlaceBlocksListener; -import world.bentobox.bentobox.listeners.flags.protection.PortalListener; -import world.bentobox.bentobox.listeners.flags.protection.ShearingListener; -import world.bentobox.bentobox.listeners.flags.protection.TNTListener; -import world.bentobox.bentobox.listeners.flags.protection.TeleportationListener; -import world.bentobox.bentobox.listeners.flags.protection.ThrowingListener; +import world.bentobox.bentobox.listeners.flags.protection.*; import world.bentobox.bentobox.listeners.flags.settings.DecayListener; import world.bentobox.bentobox.listeners.flags.settings.MobSpawnListener; import world.bentobox.bentobox.listeners.flags.settings.PVPListener; @@ -174,7 +152,10 @@ public final class Flags { public static final Flag BOAT = new Flag.Builder("BOAT", Material.OAK_BOAT).mode(Flag.Mode.BASIC).build(); public static final Flag TRADING = new Flag.Builder("TRADING", Material.EMERALD).defaultSetting(true).mode(Flag.Mode.BASIC).build(); public static final Flag NAME_TAG = new Flag.Builder("NAME_TAG", Material.NAME_TAG).mode(Flag.Mode.ADVANCED).build(); - + /** + * @since 1.21 + */ + public static final Flag ALLAY = new Flag.Builder("ALLAY", Material.AMETHYST_SHARD).mode(Flag.Mode.ADVANCED).build(); // Breeding public static final Flag BREEDING = new Flag.Builder("BREEDING", Material.CARROT).listener(new BreedingListener()).mode(Flag.Mode.ADVANCED).build(); @@ -182,8 +163,16 @@ public final class Flags { public static final Flag BUCKET = new Flag.Builder("BUCKET", Material.BUCKET).listener(new BucketListener()).mode(Flag.Mode.BASIC).build(); public static final Flag COLLECT_LAVA = new Flag.Builder("COLLECT_LAVA", Material.LAVA_BUCKET).build(); public static final Flag COLLECT_WATER = new Flag.Builder("COLLECT_WATER", Material.WATER_BUCKET).mode(Flag.Mode.ADVANCED).build(); + /** + * @since 1.21 + */ + public static final Flag COLLECT_POWDERED_SNOW = new Flag.Builder("COLLECT_POWDERED_SNOW", Material.POWDER_SNOW_BUCKET).mode(Flag.Mode.ADVANCED).build(); public static final Flag MILKING = new Flag.Builder("MILKING", Material.MILK_BUCKET).mode(Flag.Mode.ADVANCED).build(); public static final Flag FISH_SCOOPING = new Flag.Builder("FISH_SCOOPING", Material.TROPICAL_FISH_BUCKET).build(); + /** + * @since 1.21 + */ + public static final Flag AXOLOTL_SCOOPING = new Flag.Builder("AXOLOTL_SCOOPING", Material.AXOLOTL_BUCKET).build(); // Chorus Fruit and Enderpearls public static final Flag CHORUS_FRUIT = new Flag.Builder("CHORUS_FRUIT", Material.CHORUS_FRUIT).listener(new TeleportationListener()).build(); @@ -314,6 +303,32 @@ public final class Flags { .clickHandler(new CycleClick("CHANGE_SETTINGS", RanksManager.MEMBER_RANK, RanksManager.OWNER_RANK)) .mode(Flag.Mode.TOP_ROW).build(); + /** + * This flag allows choosing which island member group can activate sculk sensors. + * TODO: Enums#getIfPresent is used to support 1.18 + * @since 1.21.0 + */ + public static final Flag SCULK_SENSOR = new Flag.Builder("SCULK_SENSOR", Enums.getIfPresent(Material.class, "SCULK_SENSOR").or(Material.BARRIER)). + listener(new SculkSensorListener()). + type(Type.PROTECTION). + defaultSetting(true). + defaultRank(RanksManager.MEMBER_RANK). + clickHandler(new CycleClick("SCULK_SENSOR", RanksManager.VISITOR_RANK, RanksManager.MEMBER_RANK)). + build(); + + /** + * This flag allows choosing which island member group can activate sculk shrieker. + * TODO: Enums#getIfPresent is used to support 1.18 + * @since 1.21.0 + */ + public static final Flag SCULK_SHRIEKER = new Flag.Builder("SCULK_SHRIEKER", Enums.getIfPresent(Material.class, "SCULK_SHRIEKER").or(Material.BARRIER)). + listener(new SculkShriekerListener()). + type(Type.PROTECTION). + defaultSetting(true). + defaultRank(RanksManager.MEMBER_RANK). + clickHandler(new CycleClick("SCULK_SHRIEKER", RanksManager.VISITOR_RANK, RanksManager.MEMBER_RANK)). + build(); + /* * Settings flags (not protection flags) */ diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_18_R2/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/v1_19_R1/WorldRegeneratorImpl.java similarity index 87% rename from src/main/java/world/bentobox/bentobox/nms/v1_18_R2/WorldRegeneratorImpl.java rename to src/main/java/world/bentobox/bentobox/nms/v1_19_R1/WorldRegeneratorImpl.java index 6754ecd02..910f0d56c 100644 --- a/src/main/java/world/bentobox/bentobox/nms/v1_18_R2/WorldRegeneratorImpl.java +++ b/src/main/java/world/bentobox/bentobox/nms/v1_19_R1/WorldRegeneratorImpl.java @@ -1,10 +1,10 @@ -package world.bentobox.bentobox.nms.v1_18_R2; +package world.bentobox.bentobox.nms.v1_19_R1; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_18_R2.CraftWorld; -import org.bukkit.craftbukkit.v1_18_R2.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R1.block.data.CraftBlockData; import net.minecraft.core.BlockPosition; import net.minecraft.world.level.World; diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java index e0b9cb8d2..6b4184aa0 100644 --- a/src/main/java/world/bentobox/bentobox/util/Util.java +++ b/src/main/java/world/bentobox/bentobox/util/Util.java @@ -25,19 +25,7 @@ import org.bukkit.World.Environment; import org.bukkit.attribute.Attribute; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; -import org.bukkit.entity.Animals; -import org.bukkit.entity.Bat; -import org.bukkit.entity.EnderDragon; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Flying; -import org.bukkit.entity.IronGolem; -import org.bukkit.entity.Monster; -import org.bukkit.entity.Player; -import org.bukkit.entity.PufferFish; -import org.bukkit.entity.Shulker; -import org.bukkit.entity.Slime; -import org.bukkit.entity.Snowman; -import org.bukkit.entity.WaterMob; +import org.bukkit.entity.*; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; @@ -49,6 +37,7 @@ import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.nms.PasteHandler; import world.bentobox.bentobox.nms.WorldRegenerator; +import world.bentobox.bentobox.versions.ServerCompatibility; /** @@ -356,8 +345,19 @@ public class Util { // Bat extends Mob // Most of passive mobs extends Animals - return entity instanceof Animals || entity instanceof IronGolem || entity instanceof Snowman || + if (ServerCompatibility.getInstance().isVersion(ServerCompatibility.ServerVersion.V1_18, + ServerCompatibility.ServerVersion.V1_18_1, + ServerCompatibility.ServerVersion.V1_18_2)) + { + return entity instanceof Animals || entity instanceof IronGolem || entity instanceof Snowman || entity instanceof WaterMob && !(entity instanceof PufferFish) || entity instanceof Bat; + } + else + { + return entity instanceof Animals || entity instanceof IronGolem || entity instanceof Snowman || + entity instanceof WaterMob && !(entity instanceof PufferFish) || entity instanceof Bat || + entity instanceof Allay; + } } /* diff --git a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java index 6fd5d1011..8e534198d 100644 --- a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java +++ b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java @@ -176,28 +176,36 @@ public class ServerCompatibility { /** * @since 1.16.0 */ - V1_16_5(Compatibility.NOT_SUPPORTED), + V1_16_5(Compatibility.INCOMPATIBLE), /** * @since 1.17.0 */ - V1_17(Compatibility.NOT_SUPPORTED), + V1_17(Compatibility.INCOMPATIBLE), /** * @since 1.17.1 */ - V1_17_1(Compatibility.SUPPORTED), + V1_17_1(Compatibility.INCOMPATIBLE), /** * @since 1.19.0 */ - V1_18(Compatibility.COMPATIBLE), + V1_18(Compatibility.SUPPORTED), /** * @since 1.19.0 */ - V1_18_1(Compatibility.COMPATIBLE), + V1_18_1(Compatibility.SUPPORTED), /** * @since 1.20.1 */ - V1_18_2(Compatibility.COMPATIBLE), + V1_18_2(Compatibility.SUPPORTED), + /** + * @since 1.21.0 + */ + V1_19(Compatibility.COMPATIBLE), + /** + * @since 1.21.0 + */ + V1_19_1(Compatibility.COMPATIBLE), ; private final Compatibility compatibility; diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 51c28f0c9..977b89850 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -731,6 +731,10 @@ ranks: protection: command-is-banned: "Command is banned for visitors" flags: + ALLAY: + name: "Allay interaction" + description: "Allow giving and taking items to/from Allay" + hint: "Allay interaction disabled" ANIMAL_NATURAL_SPAWN: description: "Toggle natural animal spawning" name: "Animal natural spawn" @@ -745,6 +749,10 @@ protection: description: "Toggle interaction" name: "Armor stands" hint: "Armor stand use disabled" + AXOLOTL_SCOOPING: + name: "Axolotl Scooping" + description: "Allow scooping of axolotl using a bucket" + hint: "Axolotl scooping disabled" BEACON: description: "Toggle interaction" name: "Beacons" @@ -889,6 +897,12 @@ protection: &a (override Buckets) name: "Collect water" hint: "Water buckets disabled" + COLLECT_POWDERED_SNOW: + description: |- + &a Toggle collecting powdered snow + &a (override Buckets) + name: "Collect powdered snow" + hint: "Powdered snow buckets disabled" COMMAND_RANKS: name: "&e Command Ranks" description: "&a Configure command ranks" @@ -1271,6 +1285,18 @@ protection: &a using spawn eggs. name: "Spawn eggs on spawners" hint: "changing a spawner's entity type using spawn eggs is not allowed" + SCULK_SENSOR: + description: |- + &a Allows to change if sculk sensor + &a can be activated by visitor. + name: "Sculk Sensor" + hint: "sculk sensor activation is disabled" + SCULK_SHRIEKER: + description: |- + &a Allows to change if sculk shrieker + &a can be activated by visitor. + name: "Sculk Shrieker" + hint: "sculk shrieker activation is disabled" TNT_DAMAGE: description: |- &a Allow TNT and TNT minecarts diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index f6af7553e..d7c3bbc35 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: BentoBox main: world.bentobox.bentobox.BentoBox version: ${project.version}${build.number} -api-version: "1.17" +api-version: "1.18" authors: [tastybento, Poslovitch] contributors: ["The BentoBoxWorld Community"] diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/GeoMobLimitTabTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/GeoMobLimitTabTest.java index 2fada2569..8724383ee 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/GeoMobLimitTabTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/clicklisteners/GeoMobLimitTabTest.java @@ -110,13 +110,13 @@ public class GeoMobLimitTabTest { assertEquals("AXOLOTL", list.get(0)); // Click on AXOLOTL - tab.onClick(panel, user, ClickType.LEFT, 9); + tab.onClick(panel, user, ClickType.LEFT, 10); list.forEach(System.out::println); assertEquals(2, list.size()); assertEquals("COW", list.get(1)); assertEquals("BAT", list.get(0)); // Click on AXOLOTL again to have it added - tab.onClick(panel, user, ClickType.LEFT, 9); + tab.onClick(panel, user, ClickType.LEFT, 10); assertEquals(3, list.size()); assertEquals("BAT", list.get(0)); assertEquals("COW", list.get(1)); diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java index fb2bd2e9f..0fe2ea98c 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java @@ -69,7 +69,7 @@ public class BlockInteractionListenerTest extends AbstractCommonSetup { clickedBlocks.put(Material.WHITE_BED, Flags.BED); when(Tag.BEDS.isTagged(Material.WHITE_BED)).thenReturn(true); clickedBlocks.put(Material.BREWING_STAND, Flags.BREWING); - clickedBlocks.put(Material.CAULDRON, Flags.BREWING); + clickedBlocks.put(Material.WATER_CAULDRON, Flags.COLLECT_WATER); clickedBlocks.put(Material.BARREL, Flags.BARREL); clickedBlocks.put(Material.CHEST, Flags.CHEST); clickedBlocks.put(Material.CHEST_MINECART, Flags.CHEST); @@ -87,6 +87,7 @@ public class BlockInteractionListenerTest extends AbstractCommonSetup { clickedBlocks.put(Material.IRON_TRAPDOOR, Flags.TRAPDOOR); when(Tag.TRAPDOORS.isTagged(Material.IRON_TRAPDOOR)).thenReturn(true); clickedBlocks.put(Material.SPRUCE_FENCE_GATE, Flags.GATE); + when(Tag.FENCE_GATES.isTagged(Material.SPRUCE_FENCE_GATE)).thenReturn(true); clickedBlocks.put(Material.BLAST_FURNACE, Flags.FURNACE); clickedBlocks.put(Material.CAMPFIRE, Flags.FURNACE); clickedBlocks.put(Material.FURNACE_MINECART, Flags.FURNACE); @@ -135,6 +136,8 @@ public class BlockInteractionListenerTest extends AbstractCommonSetup { // Nothing in hand right now when(item.getType()).thenReturn(Material.AIR); when(player.getInventory()).thenReturn(inv); + when(inv.getItemInMainHand()).thenReturn(item); + when(inv.getItemInOffHand()).thenReturn(new ItemStack(Material.BUCKET)); // FlagsManager setFlags(); diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListenerTest.java index 80bd68a91..9a943f4a1 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListenerTest.java @@ -45,6 +45,8 @@ import world.bentobox.bentobox.managers.FlagsManager; import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.IslandsManager; import world.bentobox.bentobox.util.Util; +import world.bentobox.bentobox.versions.ServerCompatibility; + @RunWith(PowerMockRunner.class) @PrepareForTest( {BentoBox.class, Bukkit.class, Flags.class, Util.class }) @@ -78,6 +80,10 @@ public class MobSpawnListenerTest { when(server.getWorld("world")).thenReturn(world); when(server.getVersion()).thenReturn("BSB_Mocking"); + ServerCompatibility serverCompatibility = mock(ServerCompatibility.class); + Whitebox.setInternalState(ServerCompatibility.class, "instance", serverCompatibility); + when(serverCompatibility.getServerVersion()).thenReturn(ServerCompatibility.ServerVersion.V1_19); + PluginManager pim = mock(PluginManager.class); ItemFactory itemFactory = mock(ItemFactory.class); diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ChestDamageListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ChestDamageListenerTest.java index a42527b21..9f45b3ee1 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ChestDamageListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ChestDamageListenerTest.java @@ -19,11 +19,7 @@ import java.util.Map; import java.util.Optional; import java.util.logging.Logger; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.World; +import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.entity.Cow; import org.bukkit.entity.Entity; @@ -50,6 +46,7 @@ import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.configuration.WorldSettings; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.listeners.flags.AbstractCommonSetup; import world.bentobox.bentobox.lists.Flags; import world.bentobox.bentobox.managers.FlagsManager; import world.bentobox.bentobox.managers.IslandWorldManager; @@ -64,18 +61,25 @@ import world.bentobox.bentobox.util.Util; */ @RunWith(PowerMockRunner.class) @PrepareForTest( {Bukkit.class, BentoBox.class, Flags.class, Util.class} ) -public class ChestDamageListenerTest { +public class ChestDamageListenerTest extends AbstractCommonSetup +{ private Location location; private BentoBox plugin; private World world; + @Override @Before - public void setUp() { + public void setUp() throws Exception { + super.setUp(); + // Set up plugin plugin = mock(BentoBox.class); Whitebox.setInternalState(BentoBox.class, "instance", plugin); + // Tags + when(Tag.SHULKER_BOXES.isTagged(any(Material.class))).thenReturn(false); + Server server = mock(Server.class); world = mock(World.class); when(server.getLogger()).thenReturn(Logger.getAnonymousLogger()); @@ -161,8 +165,6 @@ public class ChestDamageListenerTest { // Util PowerMockito.mockStatic(Util.class); when(Util.getWorld(Mockito.any())).thenReturn(mock(World.class)); - - } @After diff --git a/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java index 677202abd..cd34de631 100644 --- a/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java @@ -46,7 +46,7 @@ public class FlagsManagerTest { /** * Update this value if the number of registered listeners changes */ - private static final int NUMBER_OF_LISTENERS = 50; + private static final int NUMBER_OF_LISTENERS = 52; @Mock private BentoBox plugin; @Mock From d984955af38405097a5cc96248f6b1fa54d4168d Mon Sep 17 00:00:00 2001 From: BONNe Date: Tue, 14 Jun 2022 01:18:52 +0300 Subject: [PATCH 37/74] Fixes Range Display command crashes. Spigot in 1.18 introduce new way how block particles should be displayed. However, BentoBox never fully implemented it. To avoid such issues in-future, I added checks for each particle type that requires extra data object. Fixes #1989 --- .../admin/range/AdminRangeDisplayCommand.java | 9 +- .../bentobox/bentobox/api/user/User.java | 85 +++++++++++++++++-- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeDisplayCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeDisplayCommand.java index 8e0bed0b2..1b5135fb8 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeDisplayCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeDisplayCommand.java @@ -7,10 +7,9 @@ import java.util.Map; import org.bukkit.Bukkit; import org.bukkit.Color; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.Particle; -import com.google.common.base.Enums; - import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; @@ -24,8 +23,6 @@ public class AdminRangeDisplayCommand extends CompositeCommand { private static final String DISPLAY = "display"; private static final String SHOW = "show"; private static final String HIDE = "hide"; - // Since 1.18, the Particle.BARRIER was replaced by BLOCK_MARKER - private static final Particle BARRIER = Enums.getIfPresent(Particle.class, "BARRIER").or(Enums.getIfPresent(Particle.class, "BLOCK_MARKER").or(Particle.LAVA)); // Map of users to which ranges must be displayed private final Map displayRanges = new HashMap<>(); @@ -75,7 +72,7 @@ public class AdminRangeDisplayCommand extends CompositeCommand { getIslands().getIslandAt(user.getLocation()).ifPresent(island -> { // Draw the island protected area - drawZone(user, BARRIER, null, island, island.getProtectionRange()); + drawZone(user, Particle.BLOCK_MARKER, Material.BARRIER.createBlockData(), island, island.getProtectionRange()); // Draw the default protected area if island protected zone is different if (island.getProtectionRange() != getPlugin().getIWM().getIslandProtectionRange(getWorld())) { @@ -94,7 +91,7 @@ public class AdminRangeDisplayCommand extends CompositeCommand { displayRanges.remove(user); } - private void drawZone(User user, Particle particle, Particle.DustOptions dustOptions, Island island, int range) { + private void drawZone(User user, Particle particle, Object dustOptions, Island island, int range) { Location center = island.getProtectionCenter(); // Get player Y coordinate int playerY = user.getPlayer().getLocation().getBlockY() + 1; diff --git a/src/main/java/world/bentobox/bentobox/api/user/User.java b/src/main/java/world/bentobox/bentobox/api/user/User.java index 2c241e453..2fd7ac70c 100644 --- a/src/main/java/world/bentobox/bentobox/api/user/User.java +++ b/src/main/java/world/bentobox/bentobox/api/user/User.java @@ -17,10 +17,13 @@ import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.Particle; +import org.bukkit.Vibration; import org.bukkit.World; +import org.bukkit.block.data.BlockData; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.permissions.PermissionAttachment; import org.bukkit.permissions.PermissionAttachmentInfo; @@ -597,22 +600,90 @@ public class User implements MetaDataAble { * @param y Y coordinate of the particle to display. * @param z Z coordinate of the particle to display. */ - public void spawnParticle(Particle particle, Particle.DustOptions dustOptions, double x, double y, double z) { - if (particle.equals(Particle.REDSTONE) && dustOptions == null) { - // Security check that will avoid later unexpected exceptions. - throw new IllegalArgumentException("A non-null Particle.DustOptions must be provided when using Particle.REDSTONE as particle."); + public void spawnParticle(Particle particle, Object dustOptions, double x, double y, double z) + { + // Improve particle validation. + switch (particle) + { + case REDSTONE -> + { + if (!(dustOptions instanceof Particle.DustOptions)) + { + throw new IllegalArgumentException("A non-null Particle.DustOptions must be provided when using Particle.REDSTONE as particle."); + } + } + case ITEM_CRACK -> + { + if (!(dustOptions instanceof ItemStack)) + { + throw new IllegalArgumentException("A non-null ItemStack must be provided when using Particle.ITEM_CRACK as particle."); + } + } + case BLOCK_CRACK, BLOCK_DUST, FALLING_DUST, BLOCK_MARKER -> + { + if (!(dustOptions instanceof BlockData)) + { + throw new IllegalArgumentException("A non-null BlockData must be provided when using Particle." + particle + " as particle."); + } + } + case DUST_COLOR_TRANSITION -> + { + if (!(dustOptions instanceof Particle.DustTransition)) + { + throw new IllegalArgumentException("A non-null Particle.DustTransition must be provided when using Particle.DUST_COLOR_TRANSITION as particle."); + } + } + case VIBRATION -> + { + if (!(dustOptions instanceof Vibration)) + { + throw new IllegalArgumentException("A non-null Vibration must be provided when using Particle.VIBRATION as particle."); + } + } + case SCULK_CHARGE -> + { + if (!(dustOptions instanceof Float)) + { + throw new IllegalArgumentException("A non-null Float must be provided when using Particle.SCULK_CHARGE as particle."); + } + } + case SHRIEK -> + { + if (!(dustOptions instanceof Integer)) + { + throw new IllegalArgumentException("A non-null Integer must be provided when using Particle.SHRIEK as particle."); + } + } + case LEGACY_BLOCK_CRACK, LEGACY_BLOCK_DUST, LEGACY_FALLING_DUST -> + { + if (!(dustOptions instanceof BlockData)) + { + throw new IllegalArgumentException("A non-null MaterialData must be provided when using Particle." + particle + " as particle."); + } + } } // Check if this particle is beyond the viewing distance of the server - if (player.getLocation().toVector().distanceSquared(new Vector(x,y,z)) < (Bukkit.getServer().getViewDistance()*256*Bukkit.getServer().getViewDistance())) { - if (particle.equals(Particle.REDSTONE)) { + if (this.player != null && + this.player.getLocation().toVector().distanceSquared(new Vector(x, y, z)) < + (Bukkit.getServer().getViewDistance() * 256 * Bukkit.getServer().getViewDistance())) + { + if (particle.equals(Particle.REDSTONE)) + { player.spawnParticle(particle, x, y, z, 1, 0, 0, 0, 1, dustOptions); - } else { + } + else if (dustOptions != null) + { + player.spawnParticle(particle, x, y, z, 1, dustOptions); + } + else + { player.spawnParticle(particle, x, y, z, 1); } } } + /** * Spawn particles to the player. * They are only displayed if they are within the server's view distance. From e41f5ac24f31724634559aa4a4dd07f523437a51 Mon Sep 17 00:00:00 2001 From: BONNe Date: Tue, 14 Jun 2022 02:33:35 +0300 Subject: [PATCH 38/74] Fixes a bug that prevented mobs to attack Commit 85b52f4b introduced a bug that prevented entities to target any island member. This should fix it. --- .../flags/worldsettings/InvincibleVisitorsListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListener.java index b13000a70..00e5e7b5e 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/InvincibleVisitorsListener.java @@ -171,7 +171,7 @@ public class InvincibleVisitorsListener extends FlagListener implements ClickHan if (!(e.getTarget() instanceof Player p) || !this.getIWM().inWorld(world) || e.getTarget().hasMetadata("NPC") || - this.getIslands().userIsOnIsland(world, User.getInstance(e.getEntity())) || + this.getIslands().userIsOnIsland(world, User.getInstance(e.getTarget())) || this.PVPAllowed(p.getLocation()) || e.getReason() == EntityTargetEvent.TargetReason.TARGET_DIED || !this.getIWM().getIvSettings(world).contains(DamageCause.ENTITY_ATTACK.name())) From c54358441dfc6a7df2143687b2bfd9f12ca662a3 Mon Sep 17 00:00:00 2001 From: BONNe Date: Tue, 14 Jun 2022 02:42:48 +0300 Subject: [PATCH 39/74] Raid abuse fix (#1991) * Implements new VISITOR_TRIGGER_RAID flag. This world settings flag allows toggling if visitors can or cannot start a raid on an island they are visiting. Relates to #1976 * Fixes abuse of Raid Mechanism and Mob Natural Spawn Rules. Fixes to #1976 * Simplify raid abuse detection. --- .../flags/settings/MobSpawnListener.java | 60 +++++++++++++++++- .../VisitorsStartingRaidListener.java | 61 +++++++++++++++++++ .../world/bentobox/bentobox/lists/Flags.java | 34 +++-------- src/main/resources/locales/en-US.yml | 8 +++ 4 files changed, 136 insertions(+), 27 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/VisitorsStartingRaidListener.java diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListener.java index d30cab11e..892f60459 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/settings/MobSpawnListener.java @@ -3,11 +3,13 @@ package world.bentobox.bentobox.listeners.flags.settings; import java.util.Optional; import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.entity.PufferFish; +import org.bukkit.entity.*; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.raid.RaidFinishEvent; +import org.bukkit.event.raid.RaidTriggerEvent; +import org.bukkit.potion.PotionEffectType; import world.bentobox.bentobox.api.flags.Flag; import world.bentobox.bentobox.api.flags.FlagListener; @@ -33,6 +35,60 @@ public class MobSpawnListener extends FlagListener } + /** + * This prevents to start a raid if mob spawning rules prevents it. + * @param event RaidTriggerEvent + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onRaidStartEvent(RaidTriggerEvent event) + { + // If not in the right world exit immediately. + if (!this.getIWM().inWorld(event.getWorld())) + { + return; + } + + Optional island = getIslands().getIslandAt(event.getPlayer().getLocation()); + + if (island.map(i -> !i.isAllowed(Flags.MONSTER_NATURAL_SPAWN)).orElseGet( + () -> !Flags.MONSTER_NATURAL_SPAWN.isSetForWorld(event.getWorld()))) + { + // Monster spawning is disabled on island or world. Cancel the raid. + event.setCancelled(true); + } + } + + + /** + * This removes HERO_OF_THE_VILLAGE from players that cheated victory. + * @param event RaidFinishEvent + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onRaidFinishEvent(RaidFinishEvent event) + { + // If not in the right world exit immediately. + if (!this.getIWM().inWorld(event.getWorld())) + { + return; + } + + Optional island = getIslands().getIslandAt(event.getRaid().getLocation()); + + if (island.map(i -> !i.isAllowed(Flags.MONSTER_NATURAL_SPAWN)).orElseGet( + () -> !Flags.MONSTER_NATURAL_SPAWN.isSetForWorld(event.getWorld()))) + { + // CHEATERS. PUNISH THEM. + event.getWinners().forEach(player -> + { + if (player.isOnline()) + { + player.removePotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE); + } + }); + } + } + + /** * Prevents mobs spawning naturally * @param e - event diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/VisitorsStartingRaidListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/VisitorsStartingRaidListener.java new file mode 100644 index 000000000..8d01f8917 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/VisitorsStartingRaidListener.java @@ -0,0 +1,61 @@ +// +// Created by BONNe +// Copyright - 2022 +// + + +package world.bentobox.bentobox.listeners.flags.worldsettings; + + +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.raid.RaidTriggerEvent; +import java.util.Optional; + +import world.bentobox.bentobox.api.flags.FlagListener; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.lists.Flags; +import world.bentobox.bentobox.util.Util; + + +/** + * This listener checks for island visitors that want to start a new raid. + */ +public class VisitorsStartingRaidListener extends FlagListener +{ + /** + * This method process raid allowance from visitors. + * @param event RaidTriggerEvent + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onRaidTrigger(RaidTriggerEvent event) + { + World world = Util.getWorld(event.getWorld()); + + if (!this.getIWM().inWorld(world) || Flags.VISITOR_TRIGGER_RAID.isSetForWorld(world)) + { + // If the player triggers raid non-protected world or VISITOR_TRIGGER_RAID is disabled then do nothing. + this.report(User.getInstance(event.getPlayer()), + event, + event.getPlayer().getLocation(), + Flags.VISITOR_TRIGGER_RAID, + Why.SETTING_ALLOWED_IN_WORLD); + + return; + } + + Optional island = this.getIslands().getProtectedIslandAt(event.getPlayer().getLocation()); + + if (island.isPresent() && !island.get().getMemberSet().contains(event.getPlayer().getUniqueId())) + { + event.setCancelled(true); + this.report(User.getInstance(event.getPlayer()), + event, + event.getPlayer().getLocation(), + Flags.VISITOR_TRIGGER_RAID, + Why.SETTING_NOT_ALLOWED_IN_WORLD); + } + } +} diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index 53d631335..ac5ce1a6a 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -18,30 +18,7 @@ import world.bentobox.bentobox.listeners.flags.protection.*; import world.bentobox.bentobox.listeners.flags.settings.DecayListener; import world.bentobox.bentobox.listeners.flags.settings.MobSpawnListener; import world.bentobox.bentobox.listeners.flags.settings.PVPListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.ChestDamageListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.CleanSuperFlatListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.CoarseDirtTillingListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.CreeperListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.EnderChestListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.EndermanListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.EnterExitListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.GeoLimitMobsListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.InvincibleVisitorsListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.IslandRespawnListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.ItemFrameListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.LimitMobsListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.LiquidsFlowingOutListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.NaturalSpawningOutsideRangeListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.ObsidianScoopingListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.OfflineGrowthListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.OfflineRedstoneListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.PetTeleportListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.PistonPushListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.RemoveMobsListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.SpawnerSpawnEggsListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.TreesGrowingOutsideRangeListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.VisitorKeepInventoryListener; -import world.bentobox.bentobox.listeners.flags.worldsettings.WitherListener; +import world.bentobox.bentobox.listeners.flags.worldsettings.*; import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Util; @@ -585,7 +562,14 @@ public final class Flags { * @see VisitorKeepInventoryListener */ public static final Flag VISITOR_KEEP_INVENTORY = new Flag.Builder("VISITOR_KEEP_INVENTORY", Material.TOTEM_OF_UNDYING).listener(new VisitorKeepInventoryListener()).type(Type.WORLD_SETTING).defaultSetting(false).build(); - + + /** + * Toggles whether island visitors can trigger to start a raid on another player's island. + * @since 1.21.0 + * @see VisitorsStartingRaidListener + */ + public static final Flag VISITOR_TRIGGER_RAID = new Flag.Builder("VISITOR_TRIGGER_RAID", Material.RAVAGER_SPAWN_EGG).listener(new VisitorsStartingRaidListener()).type(Type.WORLD_SETTING).defaultSetting(true).build(); + /** * Provides a list of all the Flag instances contained in this class using reflection. * Deprecated Flags are ignored. diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 977b89850..d97361f42 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1356,6 +1356,14 @@ protection: &a &a Island members still lose their items &a if they die on their own island! + VISITOR_TRIGGER_RAID: + name: "Visitors triggers raids" + description: |- + &a Toggles if visitors can start + &a a raid on an island which they are + &a visiting. + &a + &a Bad Omen effect will be removed! WITHER_DAMAGE: name: "Toggle wither damage" description: |- From 1991dc7236b9d677383ed24aa4b5436c9dd1b109 Mon Sep 17 00:00:00 2001 From: BONNe Date: Tue, 14 Jun 2022 09:09:06 +0300 Subject: [PATCH 40/74] Fixes failing test Increase flag listener counter. --- .../java/world/bentobox/bentobox/managers/FlagsManagerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java index cd34de631..761dc6330 100644 --- a/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java @@ -46,7 +46,7 @@ public class FlagsManagerTest { /** * Update this value if the number of registered listeners changes */ - private static final int NUMBER_OF_LISTENERS = 52; + private static final int NUMBER_OF_LISTENERS = 53; @Mock private BentoBox plugin; @Mock From 3cd5b0513066e91daf62acc0651257fc501f6854 Mon Sep 17 00:00:00 2001 From: Sliman4 <99507892+Sliman4@users.noreply.github.com> Date: Thu, 16 Jun 2022 14:57:42 +0300 Subject: [PATCH 41/74] Fix Actions (#1992) * Move to new EngineHub maven repository * Remove unused WorldEdit dependency * Use Java 17 in GitHub Actions * Also change maven-compiler-plugin and maven-javadoc-plugin versions to 17 --- .github/workflows/build.yml | 4 ++-- pom.xml | 15 ++------------- src/main/resources/plugin.yml | 1 - 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c3b6a629..1683344a2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,11 +13,11 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 16 + - name: Set up JDK 17 uses: actions/setup-java@v2 with: distribution: 'adopt' - java-version: '16' + java-version: '17' - name: Cache SonarCloud packages uses: actions/cache@v2 with: diff --git a/pom.xml b/pom.xml index ef331e098..2d98dd5bc 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,6 @@ 2.10.9 d5f5e0bbd8 3.0-SNAPSHOT - 7.2.5 ${build.version}-SNAPSHOT @@ -158,10 +157,6 @@ dynmap-repo https://repo.mikeprimm.com/ - - worldedit-repo - https://maven.sk89q.com/repo/ - papermc https://repo.papermc.io/repository/maven-public/ @@ -260,12 +255,6 @@ ${dynmap.version} provided - - com.sk89q.worldedit - worldedit-core - ${worldedit.version} - provided - com.github.TheBusyBiscuit @@ -348,7 +337,7 @@ maven-compiler-plugin 3.8.1 - 16 + ${java.version} @@ -398,7 +387,7 @@ maven-javadoc-plugin 3.3.0 - 16 + ${java.version} private false -Xdoclint:none diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index d7c3bbc35..6db2205f1 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -17,7 +17,6 @@ softdepend: - Vault - PlaceholderAPI - dynmap - - WorldEdit - WorldBorderAPI - BsbMongo - WorldGeneratorApi From d8fa029ac95b6ca1aa9f8c83d9d4a79ebb9f4eef Mon Sep 17 00:00:00 2001 From: BONNe Date: Thu, 16 Jun 2022 22:44:38 +0300 Subject: [PATCH 42/74] Fixes an issue where Blueprint Pasting did not send or include nether and the end blueprints times. (#1979) --- .../bentobox/blueprints/BlueprintPaster.java | 17 ++++++++----- .../bentobox/managers/BlueprintsManager.java | 25 +++++++++++-------- src/main/resources/locales/en-US.yml | 1 + 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java index 2bbee20cc..f3406a60f 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java @@ -134,11 +134,9 @@ public class BlueprintPaster { pasteState = PasteState.CHUNK_LOAD; // If this is an island OVERWORLD paste, get the island owner. - final Optional owner = Optional.ofNullable(island) - .filter(i -> location.getWorld().getEnvironment().equals(World.Environment.NORMAL)) - .map(Island::getOwner) - .map(User::getInstance); - // Tell the owner we're pasting blocks and how much time it might take + final Optional owner = Optional.ofNullable(island).map(i -> User.getInstance(i.getOwner())); + + // Tell the owner we're pasting blocks and how much time it might take owner.ifPresent(user -> tellOwner(user, blocks.size(), attached.size(), entities.size(), plugin.getSettings().getPasteSpeed())); Bits bits = new Bits(blocks, attached, entities, blocks.entrySet().iterator(), attached.entrySet().iterator(), entities.entrySet().iterator(), @@ -223,7 +221,14 @@ public class BlueprintPaster { } } else { pasteState = PasteState.DONE; - owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.done")); + + String world = switch (location.getWorld().getEnvironment()) { + case NETHER -> owner.map(user -> user.getTranslation("general.worlds.nether")).orElse(""); + case THE_END -> owner.map(user -> user.getTranslation("general.worlds.the-end")).orElse(""); + default -> owner.map(user -> user.getTranslation("general.worlds.overworld")).orElse(""); + }; + + owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.dimension-done", "[world]", world)); } } else if (pasteState.equals(PasteState.DONE)) { diff --git a/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java b/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java index 6ef338ee2..9ed648df3 100644 --- a/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java @@ -7,16 +7,7 @@ import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarFile; @@ -467,7 +458,7 @@ public class BlueprintsManager { // Paste if (bp != null) { new BlueprintPaster(plugin, bp, addon.getOverWorld(), island).paste().thenAccept(b -> pasteNether(addon, bb, island).thenAccept(b2 -> - pasteEnd(addon, bb, island).thenAccept(b3 -> Bukkit.getScheduler().runTask(plugin, task)))); + pasteEnd(addon, bb, island).thenAccept(message -> sendMessage(island)).thenAccept(b3 -> Bukkit.getScheduler().runTask(plugin, task)))); } return true; @@ -500,6 +491,18 @@ public class BlueprintsManager { return CompletableFuture.completedFuture(false); } + + /** + * This method just sends a message to the island owner that island creating is completed. + * @param island Island which owner must receive a message. + */ + private void sendMessage(Island island) { + if (island != null && island.getOwner() != null) { + final Optional owner = Optional.of(island).map(i -> User.getInstance(i.getOwner())); + owner.ifPresent(user -> user.sendMessage("commands.island.create.pasting.done")); + } + } + /** * Validate if the bundle name is valid or not * diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index d97361f42..11a0f5c89 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -496,6 +496,7 @@ commands: estimated-time: "&a Estimated time: &b [number] &a seconds." blocks: "&a Building it block by block: &b [number] &a blocks in all..." entities: "&a Filling it with entities: &b [number] &a entities in all..." + dimension-done: "&a Island in [world] is constructed." done: "&a Done! Your island is ready and waiting for you!" pick: "&2 Pick an island" unknown-blueprint: "&c That blueprint has not been loaded yet." From db323390cf1bafcf68aaae3d87026cc52a776af6 Mon Sep 17 00:00:00 2001 From: BONNe Date: Wed, 6 Jul 2022 20:20:08 +0300 Subject: [PATCH 43/74] Implements adding unknown default island flag values in settings. (#2001) * Implements option to define non-existing flags in WorldSettings. This change adds 2 new methods in WorldSettings: * WorldSettings#getDefaultIslandFlagNames * WorldSettings#getDefaultIslandSettingNames These methods replace getDefaultIslandFlags and getDefaultIslandSettings methods. Default implementation just reads values from replaced methods. Fixes #1830 * Implement conversion from flag id to actual flag object. Replaces flag assignment to new island based on flag id's. Fixes #1830 * Switch from Flag object to String object in Island class. This switch allows to keep flags that are not present in current BentoBox installation. Otherwise, unknown flags may cause an issues. Fixes #1830 * Implement FlagBooleanSerializer. This serializer converts input map of String, Boolean to map of String, Integer. This map is used to read island setting flags, and integer value is not classic boolean values. (0 for true and -1 for false). Fixes #1830 --- .../api/configuration/WorldSettings.java | 44 +++++++++++++ .../bentobox/database/objects/Island.java | 46 ++++++------- .../adapters/FlagBooleanSerializer.java | 65 +++++++++++++++++++ .../bentobox/managers/IslandWorldManager.java | 46 +++++++++---- .../bentobox/managers/IslandsManager.java | 2 +- .../bentobox/database/objects/IslandTest.java | 2 +- 6 files changed, 167 insertions(+), 38 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/database/objects/adapters/FlagBooleanSerializer.java diff --git a/src/main/java/world/bentobox/bentobox/api/configuration/WorldSettings.java b/src/main/java/world/bentobox/bentobox/api/configuration/WorldSettings.java index 787dd3281..47e2ce3c6 100644 --- a/src/main/java/world/bentobox/bentobox/api/configuration/WorldSettings.java +++ b/src/main/java/world/bentobox/bentobox/api/configuration/WorldSettings.java @@ -2,6 +2,7 @@ package world.bentobox.bentobox.api.configuration; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -31,11 +32,54 @@ public interface WorldSettings extends ConfigObject { /** * @return default rank settings for new islands + * @deprecated since 1.21 + * Map of Flag, Integer does not allow to load other plugin/addon flags. + * It cannot be replaced with Map of String, Integer due to compatibility issues. + * @see WorldSettings#getDefaultIslandFlagNames() */ + @Deprecated Map getDefaultIslandFlags(); + /** + * Return map of flags ID's linked to default rank for new island. + * This is necessary so users could specify any flag names in settings file from other plugins and addons. + * Otherwise, Flag reader would mark flag as invalid and remove it. + * Default implementation is compatibility layer so GameModes that are not upgraded still works. + * @since 1.21 + * @return default rank settings for new islands. + */ + default Map getDefaultIslandFlagNames() + { + Map flags = new HashMap<>(); + this.getDefaultIslandFlags().forEach((key, value) -> flags.put(key.getID(), value)); + return flags; + } + + /** + * @return default settings for new + * @deprecated since 1.21 + * Map of Flag, Integer does not allow to load other plugin/addon flags. + * It cannot be replaced with Map of String, Integer due to compatibility issues. + * @see WorldSettings#getDefaultIslandSettingNames() + */ + @Deprecated Map getDefaultIslandSettings(); + /** + * Return map of flags ID's linked to default settings for new island. + * This is necessary so users could specify any flag names in settings file from other plugins and addons. + * Otherwise, Flag reader would mark flag as invalid and remove it. + * Default implementation is compatibility layer so GameModes that are not upgraded still works. + * @since 1.21 + * @return default settings for new islands. + */ + default Map getDefaultIslandSettingNames() + { + Map flags = new HashMap<>(); + this.getDefaultIslandSettings().forEach((key, value) -> flags.put(key.getID(), value)); + return flags; + } + /** * Get the world difficulty * @return difficulty diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index 57dff7e6b..92c346cca 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -39,8 +39,6 @@ import world.bentobox.bentobox.api.metadata.MetaDataAble; import world.bentobox.bentobox.api.metadata.MetaDataValue; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.adapters.Adapter; -import world.bentobox.bentobox.database.objects.adapters.FlagSerializer; -import world.bentobox.bentobox.database.objects.adapters.FlagSerializer3; import world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter; import world.bentobox.bentobox.lists.Flags; import world.bentobox.bentobox.managers.RanksManager; @@ -159,9 +157,8 @@ public class Island implements DataObject, MetaDataAble { private boolean purgeProtected = false; //// Protection flags //// - @Adapter(FlagSerializer.class) @Expose - private Map flags = new HashMap<>(); + private Map flags = new HashMap<>(); //// Island History //// @Adapter(LogEntryListAdapter.class) @@ -180,9 +177,8 @@ public class Island implements DataObject, MetaDataAble { /** * Used to store flag cooldowns for this island */ - @Adapter(FlagSerializer3.class) @Expose - private Map cooldowns = new HashMap<>(); + private Map cooldowns = new HashMap<>(); /** * Commands and the rank required to use them for this island @@ -368,13 +364,13 @@ public class Island implements DataObject, MetaDataAble { * @return flag value */ public int getFlag(@NonNull Flag flag) { - return flags.computeIfAbsent(flag, k -> flag.getDefaultRank()); + return flags.computeIfAbsent(flag.getID(), k -> flag.getDefaultRank()); } /** * @return the flags */ - public Map getFlags() { + public Map getFlags() { return flags; } @@ -722,9 +718,7 @@ public class Island implements DataObject, MetaDataAble { */ @NonNull public List getVisitors() { - return Bukkit.getOnlinePlayers().stream() - .filter(player -> playerIsVisitor(player)) - .collect(Collectors.toList()); + return Bukkit.getOnlinePlayers().stream().filter(this::playerIsVisitor).collect(Collectors.toList()); } /** @@ -736,7 +730,7 @@ public class Island implements DataObject, MetaDataAble { * @see #getVisitors() */ public boolean hasVisitors() { - return Bukkit.getOnlinePlayers().stream().anyMatch(player -> playerIsVisitor(player)); + return Bukkit.getOnlinePlayers().stream().anyMatch(this::playerIsVisitor); } /** @@ -865,7 +859,7 @@ public class Island implements DataObject, MetaDataAble { * @param doSubflags - whether to set subflags */ public void setFlag(Flag flag, int value, boolean doSubflags) { - flags.put(flag, value); + flags.put(flag.getID(), value); // Subflag support if (doSubflags && flag.hasSubflags()) { // Ensure that a subflag isn't a subflag of itself or else we're in trouble! @@ -877,7 +871,7 @@ public class Island implements DataObject, MetaDataAble { /** * @param flags the flags to set */ - public void setFlags(Map flags) { + public void setFlags(Map flags) { this.flags = flags; setChanged(); } @@ -888,11 +882,13 @@ public class Island implements DataObject, MetaDataAble { */ public void setFlagsDefaults() { BentoBox plugin = BentoBox.getInstance(); - Map result = new HashMap<>(); - plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.PROTECTION)) - .forEach(f -> result.put(f, plugin.getIWM().getDefaultIslandFlags(world).getOrDefault(f, f.getDefaultRank()))); - plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.SETTING)) - .forEach(f -> result.put(f, plugin.getIWM().getDefaultIslandSettings(world).getOrDefault(f, f.getDefaultRank()))); + Map result = new HashMap<>(); + plugin.getFlagsManager().getFlags().stream(). + filter(f -> f.getType().equals(Flag.Type.PROTECTION)). + forEach(f -> result.put(f.getID(), plugin.getIWM().getDefaultIslandFlags(world).getOrDefault(f, f.getDefaultRank()))); + plugin.getFlagsManager().getFlags().stream(). + filter(f -> f.getType().equals(Flag.Type.SETTING)). + forEach(f -> result.put(f.getID(), plugin.getIWM().getDefaultIslandSettings(world).getOrDefault(f, f.getDefaultRank()))); this.setFlags(result); setChanged(); } @@ -1134,7 +1130,7 @@ public class Island implements DataObject, MetaDataAble { public void setSettingsFlag(Flag flag, boolean state, boolean doSubflags) { int newState = state ? 1 : -1; if (flag.getType().equals(Flag.Type.SETTING) || flag.getType().equals(Flag.Type.WORLD_SETTING)) { - flags.put(flag, newState); + flags.put(flag.getID(), newState); if (doSubflags && flag.hasSubflags()) { // If we have circular subflags or a flag is a subflag of itself we are in trouble! flag.getSubflags().forEach(subflag -> setSettingsFlag(subflag, state, true)); @@ -1275,10 +1271,10 @@ public class Island implements DataObject, MetaDataAble { * @since 1.6.0 */ public boolean isCooldown(Flag flag) { - if (cooldowns.containsKey(flag) && cooldowns.get(flag) > System.currentTimeMillis()) { + if (cooldowns.containsKey(flag.getID()) && cooldowns.get(flag.getID()) > System.currentTimeMillis()) { return true; } - cooldowns.remove(flag); + cooldowns.remove(flag.getID()); setChanged(); return false; } @@ -1288,21 +1284,21 @@ public class Island implements DataObject, MetaDataAble { * @param flag - Flag to cooldown */ public void setCooldown(Flag flag) { - cooldowns.put(flag, flag.getCooldown() * 1000L + System.currentTimeMillis()); + cooldowns.put(flag.getID(), flag.getCooldown() * 1000L + System.currentTimeMillis()); setChanged(); } /** * @return the cooldowns */ - public Map getCooldowns() { + public Map getCooldowns() { return cooldowns; } /** * @param cooldowns the cooldowns to set */ - public void setCooldowns(Map cooldowns) { + public void setCooldowns(Map cooldowns) { this.cooldowns = cooldowns; setChanged(); } diff --git a/src/main/java/world/bentobox/bentobox/database/objects/adapters/FlagBooleanSerializer.java b/src/main/java/world/bentobox/bentobox/database/objects/adapters/FlagBooleanSerializer.java new file mode 100644 index 000000000..cb524268d --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/database/objects/adapters/FlagBooleanSerializer.java @@ -0,0 +1,65 @@ +package world.bentobox.bentobox.database.objects.adapters; + + +import org.bukkit.configuration.MemorySection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + + +/** + * This Serializer migrates Map of String, Boolean to Map of String, Integer in serialization process. + * It is necessary because current implementation requires flags to be mapped to Integer value. + * @author BONNe + */ +public class FlagBooleanSerializer implements AdapterInterface, Map> +{ + @SuppressWarnings("unchecked") + @Override + public Map deserialize(Object object) + { + Map result = new HashMap<>(); + if (object == null) + { + return result; + } + // For YAML + if (object instanceof MemorySection section) + { + for (String key : section.getKeys(false)) + { + result.put(key, section.getBoolean(key) ? 0 : -1); + } + } + else + { + for (Entry en : ((Map) object).entrySet()) + { + result.put(en.getKey(), en.getValue() ? 0 : -1); + } + } + + return result; + } + + + @SuppressWarnings("unchecked") + @Override + public Map serialize(Object object) + { + Map result = new HashMap<>(); + + if (object == null) + { + return result; + } + + Map flags = (Map) object; + + for (Entry en : flags.entrySet()) + { + result.put(en.getKey(), en.getValue() >= 0); + } + return result; + } +} diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java index eff2e8c65..542be4c51 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java @@ -171,10 +171,13 @@ public class IslandWorldManager { } // Set default island settings - plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.PROTECTION)) - .forEach(f -> settings.getDefaultIslandFlags().putIfAbsent(f, f.getDefaultRank())); - plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.SETTING)) - .forEach(f -> settings.getDefaultIslandSettings().putIfAbsent(f, f.getDefaultRank())); + plugin.getFlagsManager().getFlags().stream(). + filter(f -> f.getType().equals(Flag.Type.PROTECTION)). + forEach(f -> settings.getDefaultIslandFlagNames().putIfAbsent(f.getID(), f.getDefaultRank())); + plugin.getFlagsManager().getFlags().stream(). + filter(f -> f.getType().equals(Flag.Type.SETTING)). + forEach(f -> settings.getDefaultIslandSettingNames().putIfAbsent(f.getID(), f.getDefaultRank())); + Bukkit.getScheduler().runTask(plugin, () -> { // Set world difficulty Difficulty diff = settings.getDifficulty(); @@ -477,7 +480,9 @@ public class IslandWorldManager { * @return Friendly name or world name if world is not a game world */ public String getFriendlyName(@NonNull World world) { - return gameModes.containsKey(world) ? gameModes.get(world).getWorldSettings().getFriendlyName() : world.getName(); + return gameModes.containsKey(world) ? + gameModes.get(world).getWorldSettings().getFriendlyName() : + world.getName(); } /** @@ -699,8 +704,11 @@ public class IslandWorldManager { * @param world - world * @return default rank settings for new islands. */ - public Map getDefaultIslandFlags(@NonNull World world) { - return gameModes.containsKey(world) ? gameModes.get(world).getWorldSettings().getDefaultIslandFlags() : Collections.emptyMap(); + public Map getDefaultIslandFlags(@NonNull World world) + { + return this.gameModes.containsKey(world) ? + this.convertToFlags(this.gameModes.get(world).getWorldSettings().getDefaultIslandFlagNames()) : + Collections.emptyMap(); } /** @@ -715,12 +723,14 @@ public class IslandWorldManager { /** * Return island setting defaults for world * - * @param world - * - world + * @param world - world * @return default settings for new islands */ - public Map getDefaultIslandSettings(@NonNull World world) { - return gameModes.containsKey(world) ? gameModes.get(world).getWorldSettings().getDefaultIslandSettings() : Collections.emptyMap(); + public Map getDefaultIslandSettings(@NonNull World world) + { + return this.gameModes.containsKey(world) ? + this.convertToFlags(this.gameModes.get(world).getWorldSettings().getDefaultIslandSettingNames()) : + Collections.emptyMap(); } public boolean isUseOwnGenerator(@NonNull World world) { @@ -921,4 +931,18 @@ public class IslandWorldManager { return gameModes.containsKey(world) && gameModes.get(world).getWorldSettings().isTeleportPlayerToIslandUponIslandCreation(); } + + /** + * This method migrates Map of String, Integer to Map of Flag, Integer. + * @param flagNamesMap Map that contains flag names to their values. + * @return Flag objects to their values. + * @since 1.21 + */ + private Map convertToFlags(Map flagNamesMap) + { + Map flagMap = new HashMap<>(); + flagNamesMap.forEach((key, value) -> + this.plugin.getFlagsManager().getFlag(key).ifPresent(flag -> flagMap.put(flag, value))); + return flagMap; + } } diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index d3419625e..4d8a298b4 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -1333,7 +1333,7 @@ public class IslandsManager { } else { // Successful load // Clean any null flags out of the island - these can occur for various reasons - island.getFlags().keySet().removeIf(f -> f.getID().startsWith("NULL_FLAG")); + island.getFlags().keySet().removeIf(f -> f.startsWith("NULL_FLAG")); } } diff --git a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java index ac8e9bfae..2c50c67f3 100644 --- a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java +++ b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java @@ -1018,7 +1018,7 @@ public class IslandTest { */ @Test public void testSetCooldowns() { - i.setCooldowns(Collections.singletonMap(Flags.BREAK_BLOCKS, 123L)); + i.setCooldowns(Collections.singletonMap(Flags.BREAK_BLOCKS.getID(), 123L)); assertFalse(i.getCooldowns().isEmpty()); } From cbd063c9e8593b7ec25baf11b4ccdea72154d752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gali=C4=87=20Mijo?= <63787231+gmijo47@users.noreply.github.com> Date: Wed, 6 Jul 2022 19:21:36 +0200 Subject: [PATCH 44/74] Check if unique name contains chars not supported in regex expression (#1998) * Check if unique name contains chars not supported in regex expression Cannot start, contain, or end with special char, cannot contain any numbers. Can only contain - for word separation * Negation fix --- .../bentobox/blueprints/conversation/NamePrompt.java | 7 +++++++ src/main/resources/locales/cs.yml | 1 + src/main/resources/locales/de.yml | 1 + src/main/resources/locales/en-US.yml | 1 + src/main/resources/locales/es.yml | 1 + src/main/resources/locales/fr.yml | 1 + src/main/resources/locales/it.yml | 1 + src/main/resources/locales/ja.yml | 1 + src/main/resources/locales/ko.yml | 1 + src/main/resources/locales/lv.yml | 1 + src/main/resources/locales/nl.yml | 1 + src/main/resources/locales/pl.yml | 1 + src/main/resources/locales/pt_BR.yml | 1 + src/main/resources/locales/ro.yml | 1 + src/main/resources/locales/tr.yml | 1 + src/main/resources/locales/vi.yml | 1 + src/main/resources/locales/zh-CN.yml | 1 + 17 files changed, 23 insertions(+) diff --git a/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java b/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java index 2677cda44..25b86506e 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java @@ -51,6 +51,13 @@ public class NamePrompt extends StringPrompt { if (ChatColor.stripColor(input).length() > 32) { context.getForWhom().sendRawMessage("Too long"); return this; + + /*Check if unique name contains chars not supported in regex expression + Cannot start, contain, or end with special char, cannot contain any numbers. + Can only contain - for word separation*/ + }else if (!ChatColor.stripColor(input).matches("^[a-zA-Z]+(?:-[a-zA-Z]+)*$")) { + context.getForWhom().sendRawMessage(user.getTranslation("commands.admin.blueprint.management.name.invalid-char-in-unique-name")); + return this; } if (bb == null || !bb.getUniqueId().equals(BlueprintsManager.DEFAULT_BUNDLE_NAME)) { // Make a uniqueid diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml index f93e4aab4..2043d0b25 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -340,6 +340,7 @@ commands: prompt: Napiš jméno, nebo 'quit' ke zrušení too-long: '&c Příliš dlouhé' pick-a-unique-name: Prosím, zvol více jedinečný název + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: Povedlo se! conversation-prefix: '>' description: diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index b433ca8e3..081a02d5f 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -391,6 +391,7 @@ commands: prompt: Gib einen Namen ein, oder 'quit' zum Beenden too-long: "&c Zu lang" pick-a-unique-name: Wähle bitte einen eindeutigeren Namen + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: Erfolg! conversation-prefix: ">" description: diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 11a0f5c89..a652a9d12 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -367,6 +367,7 @@ commands: prompt: "Enter a name, or 'quit' to quit" too-long: "&c Too long" pick-a-unique-name: "Please pick a more unique name" + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: "Success!" conversation-prefix: ">" description: diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index 14e16aab4..0079512d5 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -362,6 +362,7 @@ commands: prompt: Ingrese un nombre o 'quit' para salir too-long: "&cDemasiado largo" pick-a-unique-name: Elige un nombre más exclusivo + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: "¡Éxito!" conversation-prefix: ">" description: diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index b6f4aeb92..6257b00be 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -86,6 +86,7 @@ commands: name: conversation-prefix: ">" pick-a-unique-name: Veuillez choisir un nom plus unique + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " prompt: Entrez un nom, ou "quitter" pour quitter quit: quitter success: Succès ! diff --git a/src/main/resources/locales/it.yml b/src/main/resources/locales/it.yml index dcb6b966c..99bb6a323 100644 --- a/src/main/resources/locales/it.yml +++ b/src/main/resources/locales/it.yml @@ -78,6 +78,7 @@ commands: name: conversation-prefix: ">" pick-a-unique-name: Scegli un nome unico + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " prompt: Inserisci un nome, o 'quit' per uscire success: Successo! too-long: "&cTroppo lungo" diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml index 1c47b5edd..7726f1c9a 100644 --- a/src/main/resources/locales/ja.yml +++ b/src/main/resources/locales/ja.yml @@ -328,6 +328,7 @@ commands: prompt: 名前を入力するか、「quit」で終了します too-long: "&c長すぎる" pick-a-unique-name: よりユニークな名前を選んでください + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: 成功! conversation-prefix: ">" description: diff --git a/src/main/resources/locales/ko.yml b/src/main/resources/locales/ko.yml index 2cf7bc776..60cbb16e6 100644 --- a/src/main/resources/locales/ko.yml +++ b/src/main/resources/locales/ko.yml @@ -337,6 +337,7 @@ commands: prompt: 이름을 입력하세요, quit를 입력하여 종료할수 있습니다 too-long: "&c 너무 깁니다" pick-a-unique-name: 더 독특한 이름을 선택하십시오 + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: 완료! conversation-prefix: ">" description: diff --git a/src/main/resources/locales/lv.yml b/src/main/resources/locales/lv.yml index 307c7766a..a3e38a1cf 100644 --- a/src/main/resources/locales/lv.yml +++ b/src/main/resources/locales/lv.yml @@ -90,6 +90,7 @@ commands: name: conversation-prefix: ">" pick-a-unique-name: Lūdzu izvēlies unikālu nosaukumu + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " prompt: Ieraksti vārdu vai 'iziet', lai izietu quit: iziet success: Izdevās! diff --git a/src/main/resources/locales/nl.yml b/src/main/resources/locales/nl.yml index c473b86e1..fa1115518 100644 --- a/src/main/resources/locales/nl.yml +++ b/src/main/resources/locales/nl.yml @@ -393,6 +393,7 @@ commands: prompt: Voer een naam in of 'quit' om te stoppen too-long: "&c Te lang" pick-a-unique-name: Kies een meer unieke naam + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: Succes! conversation-prefix: ">" description: diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml index e631951cc..1cb2cd42d 100644 --- a/src/main/resources/locales/pl.yml +++ b/src/main/resources/locales/pl.yml @@ -345,6 +345,7 @@ commands: prompt: Wprowadź nazwę, lub wpisz 'wyjdź', by wyjść too-long: '&cNazwa zbyt długa' pick-a-unique-name: Wybierz bardziej unikalną nazwę + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: Sukces! conversation-prefix: '>' description: diff --git a/src/main/resources/locales/pt_BR.yml b/src/main/resources/locales/pt_BR.yml index faa0949b7..b9b60ea17 100644 --- a/src/main/resources/locales/pt_BR.yml +++ b/src/main/resources/locales/pt_BR.yml @@ -354,6 +354,7 @@ commands: prompt: Digite um nome, ou 'quit' para sair too-long: '&c Muito comprido' pick-a-unique-name: Por favor escolha um nome único + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: Sucesso! conversation-prefix: '>' description: diff --git a/src/main/resources/locales/ro.yml b/src/main/resources/locales/ro.yml index 31163db70..4095afabd 100644 --- a/src/main/resources/locales/ro.yml +++ b/src/main/resources/locales/ro.yml @@ -373,6 +373,7 @@ commands: prompt: Introduceți un nume sau „renunțați” pentru a renunța too-long: "&c Prea mult" pick-a-unique-name: Vă rugăm să alegeți un nume mai unic + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: Succes! conversation-prefix: ">" description: diff --git a/src/main/resources/locales/tr.yml b/src/main/resources/locales/tr.yml index 981a52c91..b98a4db08 100644 --- a/src/main/resources/locales/tr.yml +++ b/src/main/resources/locales/tr.yml @@ -384,6 +384,7 @@ commands: prompt: İsim gir ya da çıkmak için 'quit' yaz. too-long: "&cÇok uzun." pick-a-unique-name: Lütfen daha benzersiz bir ad seçin + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: Başarılı! conversation-prefix: ">" description: diff --git a/src/main/resources/locales/vi.yml b/src/main/resources/locales/vi.yml index eaca9f71b..5ce65a944 100644 --- a/src/main/resources/locales/vi.yml +++ b/src/main/resources/locales/vi.yml @@ -376,6 +376,7 @@ commands: prompt: Enter a name, or 'quit' to quit too-long: "&c Too long" pick-a-unique-name: Please pick a more unique name + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: Success! conversation-prefix: ">" description: diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index 595557906..9ee73b3c5 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -367,6 +367,7 @@ commands: prompt: "&e请输入新名称, 或 “&b quit&e” 来退出编辑。" too-long: "&c新名称太长了!" pick-a-unique-name: "&c这个名称已存在, 请另选一个不同的名称!" + invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " success: "&a成功!" conversation-prefix: "&3> &r" description: From f969f2721f06ced85ed1471eaa2d887e496858c2 Mon Sep 17 00:00:00 2001 From: BONNe Date: Wed, 6 Jul 2022 20:39:15 +0300 Subject: [PATCH 45/74] Disable DynmapHook as it is not implemented. Fixes #1995 --- src/main/java/world/bentobox/bentobox/BentoBox.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java index 981addb44..25bb0a268 100644 --- a/src/main/java/world/bentobox/bentobox/BentoBox.java +++ b/src/main/java/world/bentobox/bentobox/BentoBox.java @@ -227,8 +227,8 @@ public class BentoBox extends JavaPlugin { hooksManager.registerHook(new MultiverseCoreHook()); islandWorldManager.registerWorldsToMultiverse(); - // Register additional hooks - hooksManager.registerHook(new DynmapHook()); + // TODO: re-enable after implementation + //hooksManager.registerHook(new DynmapHook()); // TODO: re-enable after rework //hooksManager.registerHook(new LangUtilsHook()); From 2f244c06067a49427f8289096c4b74e9ff267c6a Mon Sep 17 00:00:00 2001 From: BONNe Date: Wed, 6 Jul 2022 22:12:47 +0300 Subject: [PATCH 46/74] Fixes an issue when portals did not operate under 0. Fixes #1977 --- .../PortalTeleportationListener.java | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/PortalTeleportationListener.java b/src/main/java/world/bentobox/bentobox/listeners/PortalTeleportationListener.java index 38b1d06ed..28a56a8f1 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/PortalTeleportationListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/PortalTeleportationListener.java @@ -167,7 +167,7 @@ public class PortalTeleportationListener implements Listener { return false; } - if (!Bukkit.getServer().getAllowNether()) { + if (!Bukkit.getAllowNether()) { e.setCancelled(true); } @@ -256,19 +256,30 @@ public class PortalTeleportationListener implements Listener { * @param env - environment * @param toWorld - to world */ - Location getTo(PlayerEntityPortalEvent e, Environment env, World toWorld) { + Location getTo(PlayerEntityPortalEvent e, Environment env, World toWorld) + { // Null check - not that useful - if (e.getFrom().getWorld() == null || toWorld == null) { + if (e.getFrom().getWorld() == null || toWorld == null) + { return null; } - if (!e.getCanCreatePortal()) { + + Location toLocation = e.getIsland().map(island -> island.getSpawnPoint(env)). + orElse(e.getFrom().toVector().toLocation(toWorld)); + + // Limit Y to the min/max world height. + toLocation.setY(Math.max(Math.min(toLocation.getY(), toWorld.getMaxHeight()), toWorld.getMinHeight())); + + if (!e.getCanCreatePortal()) + { // Legacy portaling - return e.getIsland().map(i -> i.getSpawnPoint(env)).orElse(e.getFrom().toVector().toLocation(toWorld)); + return toLocation; } // Make portals // For anywhere other than the end - it is the player's location that is used - if (!env.equals(Environment.THE_END)) { - return e.getFrom().toVector().toLocation(toWorld); + if (!env.equals(Environment.THE_END)) + { + return toLocation; } // If the-end then we want the platform to always be generated in the same place no matter where // they enter the portal @@ -279,13 +290,15 @@ public class PortalTeleportationListener implements Listener { int j = z; int k = y; // If the from is not a portal, then we have to find it - if (!e.getFrom().getBlock().getType().equals(Material.END_PORTAL)) { + if (!e.getFrom().getBlock().getType().equals(Material.END_PORTAL)) + { // Find the portal - due to speed, it is possible that the player will be below or above the portal - for (k = 0; (k < e.getWorld().getMaxHeight()) && !e.getWorld().getBlockAt(x, k, z).getType().equals(Material.END_PORTAL); k++); + for (k = toWorld.getMinHeight(); (k < e.getWorld().getMaxHeight()) && + !e.getWorld().getBlockAt(x, k, z).getType().equals(Material.END_PORTAL); k++); } // Find the maximum x and z corner - for (; (i < x + 5) && e.getWorld().getBlockAt(i, k, z).getType().equals(Material.END_PORTAL); i++); - for (; (j < z + 5) && e.getWorld().getBlockAt(x, k, j).getType().equals(Material.END_PORTAL); j++); + for (; (i < x + 5) && e.getWorld().getBlockAt(i, k, z).getType().equals(Material.END_PORTAL); i++) ; + for (; (j < z + 5) && e.getWorld().getBlockAt(x, k, j).getType().equals(Material.END_PORTAL); j++) ; // Mojang end platform generation is: // AIR @@ -293,7 +306,7 @@ public class PortalTeleportationListener implements Listener { // OBSIDIAN // and player is placed on second air block above obsidian. // If Y coordinate is below 2, then obsidian platform is not generated and player falls in void. - return new Location(toWorld, i, Math.max(2, k), j); + return new Location(toWorld, i, Math.max(toWorld.getMinHeight() + 2, k), j); } @@ -304,7 +317,9 @@ public class PortalTeleportationListener implements Listener { * @return true or false */ private boolean isMakePortals(GameModeAddon gm, Environment env) { - return env.equals(Environment.NETHER) ? gm.getWorldSettings().isMakeNetherPortals() : gm.getWorldSettings().isMakeEndPortals(); + return env.equals(Environment.NETHER) ? + gm.getWorldSettings().isMakeNetherPortals() && Bukkit.getAllowNether() : + gm.getWorldSettings().isMakeEndPortals() && Bukkit.getAllowEnd(); } /** From 3784aa48d72c41b69b1c9fc6247faa1d07b231f1 Mon Sep 17 00:00:00 2001 From: BONNe Date: Wed, 6 Jul 2022 23:00:20 +0300 Subject: [PATCH 47/74] Disables podzol and coarse dirt from becoming path. This protection reuses Flags.COARSE_DIRT_TILLING. Fixes #1878 --- .../CoarseDirtTillingListener.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CoarseDirtTillingListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CoarseDirtTillingListener.java index 0543e800b..d8830dcea 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CoarseDirtTillingListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/CoarseDirtTillingListener.java @@ -35,6 +35,28 @@ public class CoarseDirtTillingListener extends FlagListener { } } + + /** + * Protect Coarse dirt and podzol from being made as dirt path block. + * @param e PlayerInteractEvent + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onDirtPathCreation(PlayerInteractEvent e) + { + if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK) && + e.getItem() != null && + this.getIWM().inWorld(e.getClickedBlock().getWorld()) && + (e.getClickedBlock().getType().equals(Material.COARSE_DIRT) || e.getClickedBlock().getType().equals(Material.PODZOL)) && + e.getItem().getType().name().endsWith("_SHOVEL") && + !Flags.COARSE_DIRT_TILLING.isSetForWorld(e.getClickedBlock().getWorld())) + { + e.setCancelled(true); + User user = User.getInstance(e.getPlayer()); + user.notify("protection.protected", TextVariables.DESCRIPTION, user.getTranslation(Flags.COARSE_DIRT_TILLING.getHintReference())); + } + } + + /** * If podzol is mined when coarse dirt tilling is not allowed, then it'll just drop podzol and not dirt * This prevents an exploit where growing big spruce trees can turn gravel into podzol. From 9643f617b63eee182131ff80a04f4c4f2a54cb52 Mon Sep 17 00:00:00 2001 From: BONNe Date: Wed, 6 Jul 2022 23:30:51 +0300 Subject: [PATCH 48/74] Implement a command that allows to reset island name. Fixes #1879 --- .../api/commands/CompositeCommand.java | 8 ++ .../commands/admin/AdminResetNameCommand.java | 129 ++++++++++++++++++ .../commands/admin/DefaultAdminCommand.java | 2 + src/main/resources/locales/en-US.yml | 3 + 4 files changed, 142 insertions(+) create mode 100644 src/main/java/world/bentobox/bentobox/api/commands/admin/AdminResetNameCommand.java diff --git a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java index a4c9942ce..7b86313ed 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java @@ -309,6 +309,14 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi return plugin.getIslands(); } + /** + * Convenience method to get the island manager + * @return IslandsManager + */ + protected IslandsManager getIslandsManager() { + return plugin.getIslandsManager(); + } + /** * @return this command's sub-level. Top level is 0. * Every time a command registers with a parent, their level will be set. diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminResetNameCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminResetNameCommand.java new file mode 100644 index 000000000..852b1d92d --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/AdminResetNameCommand.java @@ -0,0 +1,129 @@ +package world.bentobox.bentobox.api.commands.admin; + +import org.eclipse.jdt.annotation.Nullable; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +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.database.objects.Island; +import world.bentobox.bentobox.util.Util; + + +/** + * This command resets players island name. + * @author BONNe + */ +public class AdminResetNameCommand extends CompositeCommand +{ + /** + * Default constructor. + * @param command Parent command. + */ + public AdminResetNameCommand(CompositeCommand command) + { + super(command, "resetname"); + } + + + /** + * {@inheritDoc} + */ + @Override + public void setup() + { + this.setPermission("mod.resetname"); + this.setOnlyPlayer(true); + this.setDescription("commands.admin.resetname.description"); + } + + + /** + * @param user the {@link User} who is executing this command. + * @param label the label which has been used to execute this command. + * It can be {@link CompositeCommand#getLabel()} or an alias. + * @param args the command arguments. + * @return {@code true} if name can be reset, {@code false} otherwise. + */ + @Override + public boolean canExecute(User user, String label, List args) + { + if (args.size() == 1) + { + UUID playerUUID = Util.getUUID(args.get(0)); + + if (playerUUID == null) + { + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); + return false; + } + + this.island = this.getIslandsManager().getIsland(this.getWorld(), playerUUID); + } + else + { + this.island = this.getIslandsManager().getIslandAt(user.getLocation()).orElse(null); + } + + if (this.island == null) + { + user.sendMessage("general.errors.player-has-no-island"); + return false; + } + + return true; + } + + + /** + * @param user the {@link User} who is executing this command. + * @param label the label which has been used to execute this command. + * It can be {@link CompositeCommand#getLabel()} or an alias. + * @param args the command arguments. + * @return {@code true} + */ + @Override + public boolean execute(User user, String label, List args) + { + if (this.island == null) + { + this.showHelp(this, user); + return true; + } + + // Resets the island name + this.island.setName(null); + user.sendMessage("commands.admin.resetname.success", TextVariables.NAME, this.getPlayers().getName(this.island.getOwner())); + return true; + } + + + /** + * @param user the {@link User} who is executing this command. + * @param alias alias for command + * @param args command arguments + * @return Optional of possible values. + */ + @Override + public Optional> tabComplete(User user, String alias, List args) { + // Return the player names + + if (args.size() == 1) + { + return Optional.of(Util.getOnlinePlayerList(user)); + } + else + { + return Optional.empty(); + } + } + + + /** + * Island which name must be changed. + */ + @Nullable + private Island island; +} diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java index fd1096591..beb8859e4 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java @@ -94,6 +94,8 @@ public abstract class DefaultAdminCommand extends CompositeCommand { new AdminSetProtectionCenterCommand(this); // Delete homes new AdminDeleteHomesCommand(this); + // Reset name + new AdminResetNameCommand(this); } /** diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index a652a9d12..1dd4f683b 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -422,6 +422,9 @@ commands: description: "removes deaths to the player" parameters: " " success: "&a Successfully removed &b [number] &a deaths to &b [name], decreasing the total to &b [total]&a deaths." + resetname: + description: "reset player island name" + success: "&a Successfully reset [name]'s island name." bentobox: description: "BentoBox admin command" about: From 9ec8730359840da8f4469c571461f1cc3921095b Mon Sep 17 00:00:00 2001 From: BONNe Date: Wed, 6 Jul 2022 23:55:17 +0300 Subject: [PATCH 49/74] Prevents liquids from being placed from dispenser outside island area. Fixes #1869 --- .../bentobox/api/flags/FlagListener.java | 8 ++++ .../LiquidsFlowingOutListener.java | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/main/java/world/bentobox/bentobox/api/flags/FlagListener.java b/src/main/java/world/bentobox/bentobox/api/flags/FlagListener.java index c1178a7d7..10c999abe 100644 --- a/src/main/java/world/bentobox/bentobox/api/flags/FlagListener.java +++ b/src/main/java/world/bentobox/bentobox/api/flags/FlagListener.java @@ -256,6 +256,14 @@ public abstract class FlagListener implements Listener { return plugin.getIslands(); } + /** + * Get the island database manager + * @return the island database manager + */ + protected IslandsManager getIslandsManager() { + return plugin.getIslands(); + } + /** * Get the island world manager * @return Island World Manager diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/LiquidsFlowingOutListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/LiquidsFlowingOutListener.java index 3c533adbf..bf6b90835 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/LiquidsFlowingOutListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/LiquidsFlowingOutListener.java @@ -2,9 +2,12 @@ package world.bentobox.bentobox.listeners.flags.worldsettings; import java.util.Optional; +import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDispenseEvent; import org.bukkit.event.block.BlockFromToEvent; import world.bentobox.bentobox.api.flags.FlagListener; @@ -43,4 +46,38 @@ public class LiquidsFlowingOutListener extends FlagListener { } } + + /** + * Prevents players from dispensing water, lava and powdered snow from dispenser outside island + * if Flags.LIQUIDS_FLOWING_OUT is disabled. + * @param event BlockDispenseEvent + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onDispenserLiquid(BlockDispenseEvent event) + { + Location from = event.getBlock().getLocation(); + + if (!this.getIWM().inWorld(from) || Flags.LIQUIDS_FLOWING_OUT.isSetForWorld(from.getWorld())) { + // We do not want to run any check if this is not the right world or if it is allowed. + return; + } + + Location to = event.getVelocity().toLocation(event.getBlock().getWorld()); + + if (!event.getItem().getType().equals(Material.WATER_BUCKET) && + !event.getItem().getType().equals(Material.LAVA_BUCKET) && + !event.getItem().getType().equals(Material.POWDER_SNOW_BUCKET)) + { + return; + } + + // Only prevent if it is flowing into the area between islands or into another island. + Optional fromIsland = this.getIslandsManager().getProtectedIslandAt(from); + Optional toIsland = this.getIslandsManager().getProtectedIslandAt(to); + + if (toIsland.isEmpty() || (fromIsland.isPresent() && !fromIsland.equals(toIsland))) + { + event.setCancelled(true); + } + } } From 927fcba15a3a4c7ccf728b89d92c33aa3d651088 Mon Sep 17 00:00:00 2001 From: BONNe Date: Thu, 7 Jul 2022 00:08:15 +0300 Subject: [PATCH 50/74] Disables biome copying by default in blueprints. Biome will be copied only if it is required by copy command parameter. Fixes #1862 --- .../blueprints/AdminBlueprintCopyCommand.java | 37 ++++++++++++++----- .../blueprints/BlueprintClipboard.java | 23 +++++++----- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCopyCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCopyCommand.java index 022b857d0..8b54ac28f 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCopyCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCopyCommand.java @@ -1,35 +1,54 @@ package world.bentobox.bentobox.api.commands.admin.blueprints; import java.util.List; +import java.util.Optional; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.blueprints.BlueprintClipboard; -public class AdminBlueprintCopyCommand extends CompositeCommand { - public AdminBlueprintCopyCommand(AdminBlueprintCommand parent) { +public class AdminBlueprintCopyCommand extends CompositeCommand +{ + public AdminBlueprintCopyCommand(AdminBlueprintCommand parent) + { super(parent, "copy"); } + @Override - public void setup() { + public void setup() + { inheritPermission(); setParametersHelp("commands.admin.blueprint.copy.parameters"); setDescription("commands.admin.blueprint.copy.description"); } + @Override - public boolean execute(User user, String label, List args) { - if (args.size() > 1) { - showHelp(this, user); + public boolean execute(User user, String label, List args) + { + if (args.size() > 2) + { + this.showHelp(this, user); return false; } AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent(); - BlueprintClipboard clipboard = parent.getClipboards().computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard()); - boolean copyAir = (args.size() == 1 && args.get(0).equalsIgnoreCase("air")); - return clipboard.copy(user, copyAir); + BlueprintClipboard clipboard = + parent.getClipboards().computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard()); + + boolean copyAir = args.stream().anyMatch(key -> key.equalsIgnoreCase("air")); + boolean copyBiome = args.stream().anyMatch(key -> key.equalsIgnoreCase("biome")); + + return clipboard.copy(user, copyAir, copyBiome); + } + + + @Override + public Optional> tabComplete(User user, String alias, List args) + { + return Optional.of(List.of("air", "biome")); } } diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java index a3daa230c..fb8ac322c 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java @@ -85,7 +85,7 @@ public class BlueprintClipboard { * @param user - user * @return true if successful, false if pos1 or pos2 are undefined. */ - public boolean copy(User user, boolean copyAir) { + public boolean copy(User user, boolean copyAir, boolean copyBiome) { if (copying) { user.sendMessage("commands.admin.blueprint.mid-copy"); return false; @@ -120,11 +120,11 @@ public class BlueprintClipboard { int speed = plugin.getSettings().getPasteSpeed(); List vectorsToCopy = getVectors(toCopy); - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> copyAsync(world, user, vectorsToCopy, speed, copyAir)); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> copyAsync(world, user, vectorsToCopy, speed, copyAir, copyBiome)); return true; } - private void copyAsync(World world, User user, List vectorsToCopy, int speed, boolean copyAir) { + private void copyAsync(World world, User user, List vectorsToCopy, int speed, boolean copyAir, boolean copyBiome) { copying = false; copyTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { if (copying) { @@ -139,7 +139,7 @@ public class BlueprintClipboard { Math.rint(e.getLocation().getY()), Math.rint(e.getLocation().getZ())).equals(v)) .collect(Collectors.toList()); - if (copyBlock(v.toLocation(world), copyAir, ents)) { + if (copyBlock(v.toLocation(world), copyAir, copyBiome, ents)) { count++; } }); @@ -179,7 +179,7 @@ public class BlueprintClipboard { return r; } - private boolean copyBlock(Location l, boolean copyAir, Collection entities) { + private boolean copyBlock(Location l, boolean copyAir, boolean copyBiome, Collection entities) { Block block = l.getBlock(); if (!copyAir && block.getType().equals(Material.AIR) && entities.isEmpty()) { return false; @@ -203,20 +203,23 @@ public class BlueprintClipboard { return true; } - - BlueprintBlock b = bluePrintBlock(pos, block); + BlueprintBlock b = bluePrintBlock(pos, block, copyBiome); if (b != null) { this.bpBlocks.put(pos, b); } return true; } - private BlueprintBlock bluePrintBlock(Vector pos, Block block) { + private BlueprintBlock bluePrintBlock(Vector pos, Block block, boolean copyBiome) { // Block state BlockState blockState = block.getState(); BlueprintBlock b = new BlueprintBlock(block.getBlockData().getAsString()); - // Biome - b.setBiome(block.getBiome()); + + if (copyBiome) { + // Biome + b.setBiome(block.getBiome()); + } + // Signs if (blockState instanceof Sign sign) { b.setSignLines(Arrays.asList(sign.getLines())); From aed8caeb7608c27e1b4dae128588ee11bec8d79c Mon Sep 17 00:00:00 2001 From: BONNe Date: Sun, 10 Jul 2022 01:44:15 +0300 Subject: [PATCH 51/74] Tweak translation for skulk sensor and shrieker. --- src/main/resources/locales/en-US.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 1dd4f683b..0e3899399 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1292,14 +1292,14 @@ protection: hint: "changing a spawner's entity type using spawn eggs is not allowed" SCULK_SENSOR: description: |- - &a Allows to change if sculk sensor - &a can be activated by visitor. + &a Toggles sculk sensor + &a activation. name: "Sculk Sensor" hint: "sculk sensor activation is disabled" SCULK_SHRIEKER: description: |- - &a Allows to change if sculk shrieker - &a can be activated by visitor. + &a Toggles sculk shrieker + &a activation. name: "Sculk Shrieker" hint: "sculk shrieker activation is disabled" TNT_DAMAGE: From 5a527d411979d01387f73f7cecda476dc825612b Mon Sep 17 00:00:00 2001 From: BONNe Date: Wed, 13 Jul 2022 12:35:20 +0300 Subject: [PATCH 52/74] Add parent for admin team's commands. This is a parity change with player command. --- .../commands/admin/DefaultAdminCommand.java | 7 +-- .../commands/admin/team/AdminTeamCommand.java | 47 +++++++++++++++++++ src/main/resources/locales/en-US.yml | 1 + 3 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamCommand.java diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java index beb8859e4..f9a952e60 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java @@ -9,11 +9,7 @@ import world.bentobox.bentobox.api.commands.admin.deaths.AdminDeathsCommand; import world.bentobox.bentobox.api.commands.admin.purge.AdminPurgeCommand; import world.bentobox.bentobox.api.commands.admin.range.AdminRangeCommand; import world.bentobox.bentobox.api.commands.admin.resets.AdminResetsCommand; -import world.bentobox.bentobox.api.commands.admin.team.AdminTeamAddCommand; -import world.bentobox.bentobox.api.commands.admin.team.AdminTeamDisbandCommand; -import world.bentobox.bentobox.api.commands.admin.team.AdminTeamFixCommand; -import world.bentobox.bentobox.api.commands.admin.team.AdminTeamKickCommand; -import world.bentobox.bentobox.api.commands.admin.team.AdminTeamSetownerCommand; +import world.bentobox.bentobox.api.commands.admin.team.*; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; @@ -56,6 +52,7 @@ public abstract class DefaultAdminCommand extends CompositeCommand { new AdminSetrankCommand(this); new AdminInfoCommand(this); // Team commands + new AdminTeamCommand(this); new AdminTeamAddCommand(this); new AdminTeamKickCommand(this); new AdminTeamDisbandCommand(this); diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamCommand.java new file mode 100644 index 000000000..b66989b8b --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamCommand.java @@ -0,0 +1,47 @@ +// +// Created by BONNe +// Copyright - 2022 +// + + +package world.bentobox.bentobox.api.commands.admin.team; + + +import java.util.List; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; + + +/** + * Parent command for all Admin Team commands. + */ +public class AdminTeamCommand extends CompositeCommand +{ + public AdminTeamCommand(CompositeCommand parent) + { + super(parent, "team"); + } + + + @Override + public void setup() + { + this.setPermission("mod.team"); + this.setDescription("commands.admin.team.description"); + + new AdminTeamAddCommand(this); + new AdminTeamDisbandCommand(this); + new AdminTeamFixCommand(this); + new AdminTeamKickCommand(this); + new AdminTeamSetownerCommand(this); + } + + + @Override + public boolean execute(User user, String label, List args) + { + this.showHelp(this, user); + return true; + } +} diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 0e3899399..1616bfe77 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -102,6 +102,7 @@ commands: status: "&b [purged] &a islands purged out of &b [purgeable] &7(&b[percentage] %&7)&a." team: + description: "manage teams" add: parameters: " " description: "add player to owner's team" From a59697c14e1b537357fc8d6a615d16eeeaf5b466 Mon Sep 17 00:00:00 2001 From: BONNe Date: Thu, 14 Jul 2022 11:29:46 +0300 Subject: [PATCH 53/74] Fixes particle spawning for older API usage. Border addon used still older BentoBox API. This change fixes it, and fixes API compatibility with older addons/plugins. --- .../bentobox/bentobox/api/user/User.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/user/User.java b/src/main/java/world/bentobox/bentobox/api/user/User.java index 2fd7ac70c..11a7e593f 100644 --- a/src/main/java/world/bentobox/bentobox/api/user/User.java +++ b/src/main/java/world/bentobox/bentobox/api/user/User.java @@ -687,6 +687,7 @@ public class User implements MetaDataAble { /** * Spawn particles to the player. * They are only displayed if they are within the server's view distance. + * Compatibility method for older usages. * @param particle Particle to display. * @param dustOptions Particle.DustOptions for the particle to display. * Cannot be null when particle is {@link Particle#REDSTONE}. @@ -694,10 +695,28 @@ public class User implements MetaDataAble { * @param y Y coordinate of the particle to display. * @param z Z coordinate of the particle to display. */ - public void spawnParticle(Particle particle, Particle.DustOptions dustOptions, int x, int y, int z) { - spawnParticle(particle, dustOptions, (double) x, (double) y, (double) z); + public void spawnParticle(Particle particle, Particle.DustOptions dustOptions, double x, double y, double z) + { + this.spawnParticle(particle, (Object) dustOptions, x, y, z); } + + /** + * Spawn particles to the player. + * They are only displayed if they are within the server's view distance. + * @param particle Particle to display. + * @param dustOptions Particle.DustOptions for the particle to display. + * Cannot be null when particle is {@link Particle#REDSTONE}. + * @param x X coordinate of the particle to display. + * @param y Y coordinate of the particle to display. + * @param z Z coordinate of the particle to display. + */ + public void spawnParticle(Particle particle, Particle.DustOptions dustOptions, int x, int y, int z) + { + this.spawnParticle(particle, dustOptions, (double) x, (double) y, (double) z); + } + + /* (non-Javadoc) * @see java.lang.Object#hashCode() */ From 64b4c43742a421673801fe460033ce42f299df63 Mon Sep 17 00:00:00 2001 From: BONNe Date: Mon, 25 Jul 2022 00:16:16 +0300 Subject: [PATCH 54/74] Revert 6e734fc3432b1d435d1c02a462021d25195ec4af The commit was wrong. Protection range should not be decreased, as that would mean that max X and Z blocks are not included in island protection bounding box. This change is the same as 3c65194dfb332ac6beaf8d956bbf4350c0cd9fa5 but was missed when it was fixed. Relate to #473 --- .../world/bentobox/bentobox/database/objects/Island.java | 7 ++++++- .../bentobox/bentobox/database/objects/IslandTest.java | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index 92c346cca..bfd03d62b 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -811,7 +811,12 @@ public class Island implements DataObject, MetaDataAble { * @since 1.5.2 */ public BoundingBox getProtectionBoundingBox() { - return new BoundingBox(getMinProtectedX(), world.getMinHeight(), getMinProtectedZ(), getMaxProtectedX()-1.0D, world.getMaxHeight(), getMaxProtectedZ()-1.0D); + return new BoundingBox(this.getMinProtectedX(), + this.world.getMinHeight(), + this.getMinProtectedZ(), + this.getMaxProtectedX(), + this.world.getMaxHeight(), + this.getMaxProtectedZ()); } /** diff --git a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java index 2c50c67f3..8e86e979d 100644 --- a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java +++ b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java @@ -24,6 +24,7 @@ import org.bukkit.Material; import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.block.Block; +import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; import org.junit.After; import org.junit.Before; @@ -564,6 +565,7 @@ public class IslandTest { public void testGetProtectionBoundingBox() { i.setWorld(world); assertNotNull(i.getProtectionBoundingBox()); + assertEquals("BoundingBox [minX=-100.0, minY=0.0, minZ=-100.0, maxX=100.0, maxY=0.0, maxZ=100.0]", i.getProtectionBoundingBox().toString()); } /** From 44201afa1f8810579e7bf408500d7245a48db716 Mon Sep 17 00:00:00 2001 From: BONNe Date: Wed, 3 Aug 2022 19:00:42 +0300 Subject: [PATCH 55/74] Fixes an issue with missing parent permission check before command execution. Implement a new method that recursively checks if player has access permission to all commands in whole hierarchy. Fixes #2010 --- .../api/commands/CompositeCommand.java | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java index 7b86313ed..cb3e4ca9d 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java @@ -262,17 +262,44 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi user.sendMessage("general.errors.use-in-game"); return false; } - // Check perms, but only if this isn't the console - if (user.isPlayer() && !user.isOp() && getPermission() != null && !getPermission().isEmpty() && !user.hasPermission(getPermission())) { - user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, getPermission()); + + if (!this.runPermissionCheck(user)) + { + // Error message is displayed by permission check. return false; } + // Set the user's addon context user.setAddon(addon); // Execute and trim args return canExecute(user, cmdLabel, cmdArgs) && execute(user, cmdLabel, cmdArgs); } + + /** + * This method checks and returns if user has access to the called command. + * It also recursively checks if user has access to the all parent commands. + * @param user User who permission must be checked. + * @return {@code true} is user can execute given command, {@code false} otherwise. + */ + private boolean runPermissionCheck(User user) + { + // Check perms, but only if this isn't the console + if (user.isPlayer() && + !user.isOp() && + this.getPermission() != null && + !this.getPermission().isEmpty() && + !user.hasPermission(this.getPermission())) + { + user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, this.getPermission()); + return false; + } + + // Recursive permission check to find if user has access to the parent command. + return this.getParent() == null || this.getParent().runPermissionCheck(user); + } + + /** * Get the current composite command based on the arguments * @param args - arguments From 6c1399c2b4225fe6685f338eef0dbf836b0ddeb2 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sun, 14 Aug 2022 20:12:15 +0300 Subject: [PATCH 56/74] Fixes wrong admin permission. Default Amin command should have `admin` instead of admin.* --- .../bentobox/api/commands/admin/DefaultAdminCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java index f9a952e60..81192d164 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java @@ -38,7 +38,7 @@ public abstract class DefaultAdminCommand extends CompositeCommand { */ @Override public void setup() { - this.setPermission("admin.*"); + this.setPermission("admin"); this.setOnlyPlayer(false); this.setParametersHelp("commands.admin.help.parameters"); From 6f791420e7cbce1d2eb8acf9af22f9c817e8fb8c Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 26 Aug 2022 11:32:03 +0300 Subject: [PATCH 57/74] Proper dimension processing (#2015) * Adds ProtectionBoundingBox for environment Since minecraft introduced different island heights for different dimensions, protection bounding box were returned wrong area for nether and end dimensions. This change adds a new method that allows to get proper protection bounding box for requested dimension. Due to the fact, that nether and end islands can be disabled, then this method may return null. Fixes #2014 * Adds BoundingBox for environment Since minecraft introduced different island heights for different dimensions, bounding box were returned wrong area for nether and end dimensions. This change adds a new method that allows to get proper bounding box for requested dimension. Due to the fact, that nether and end islands can be disabled, then this method may return null. Part of #2014 * Fixes Island#onIsland check for non-island worlds Island#onIsland method was missing checks if island mode is enabled for requested dimension. It returned false positive situations in cases when island generation were disabled in nether or the end worlds. * Fixes Island#inIslandSpace check for non-island worlds Island#inIslandSpace method was missing checks if island mode is enabled for requested dimension. It returned false positive situations in cases when island generation were disabled in nether or the end worlds. * Adds some helper methods in Island object. - Island#getNetherWorld - returns the nether world or null - Island#getEndWorld - returns the end world or null - Island#getWorld(Environment) - returns world of requested environment or null - Island#isNetherIslandEnabled - returns if nether is generated and nether islands are enabled. - Island#isEndIslandEnabled - returns if end is generated and end islands are enabled. --- .../bentobox/database/objects/Island.java | 231 ++++++++++++++++-- 1 file changed, 209 insertions(+), 22 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index bfd03d62b..b22934d26 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -640,6 +640,55 @@ public class Island implements DataObject, MetaDataAble { return world; } + + /** + * @return the nether world + */ + @Nullable + public World getNetherWorld() + { + return this.getWorld(Environment.NETHER); + } + + + /** + * @return the end world + */ + @Nullable + public World getEndWorld() + { + return this.getWorld(Environment.THE_END); + } + + + /** + * This method returns this island world in given environment. This method can return {@code null} if dimension is + * disabled. + * @param environment The environment of the island world. + * @return the world in given environment. + */ + @Nullable + public World getWorld(Environment environment) + { + if (Environment.NORMAL.equals(environment)) + { + return this.world; + } + else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled()) + { + return this.getPlugin().getIWM().getEndWorld(this.world); + } + else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled()) + { + return this.getPlugin().getIWM().getNetherWorld(this.world); + } + else + { + return null; + } + } + + /** * @return the x coordinate of the island center */ @@ -676,8 +725,13 @@ public class Island implements DataObject, MetaDataAble { * @param location - location * @return true if in island space */ + @SuppressWarnings("ConstantConditions") public boolean inIslandSpace(Location location) { - return Util.sameWorld(world, location.getWorld()) && inIslandSpace(location.getBlockX(), location.getBlockZ()); + return Util.sameWorld(this.world, location.getWorld()) && + (location.getWorld().getEnvironment().equals(Environment.NORMAL) || + this.getPlugin().getIWM().isIslandNether(location.getWorld()) || + this.getPlugin().getIWM().isIslandEnd(location.getWorld())) && + this.inIslandSpace(location.getBlockX(), location.getBlockZ()); } /** @@ -690,26 +744,81 @@ public class Island implements DataObject, MetaDataAble { } /** - * Returns a {@link BoundingBox} of the full island space. + * Returns a {@link BoundingBox} of the full island space for overworld. * @return a {@link BoundingBox} of the full island space. * @since 1.5.2 */ + @SuppressWarnings("ConstantConditions") + @NotNull public BoundingBox getBoundingBox() { - return new BoundingBox(getMinX(), world.getMinHeight(), getMinZ(), getMaxX(), world.getMaxHeight(), getMaxZ()); + return this.getBoundingBox(Environment.NORMAL); } - /** - * Using this method in the filtering for getVisitors and hasVisitors - * @param player - * @return true if player is a visitor - */ - private boolean playerIsVisitor(Player player) { - if (player.getGameMode() == GameMode.SPECTATOR) { - return false; - } - return onIsland(player.getLocation()) && getRank(User.getInstance(player)) == RanksManager.VISITOR_RANK; - } + /** + * Returns a {@link BoundingBox} of this island's space area in requested dimension. + * @param environment the requested dimension. + * @return a {@link BoundingBox} of this island's space area or {@code null} if island is not created in requested dimension. + * @since 1.21.0 + */ + @Nullable + public BoundingBox getBoundingBox(Environment environment) + { + BoundingBox boundingBox; + + if (Environment.NORMAL.equals(environment)) + { + // Return normal world bounding box. + boundingBox = new BoundingBox(this.getMinX(), + this.world.getMinHeight(), + this.getMinZ(), + this.getMaxX(), + this.world.getMaxHeight(), + this.getMaxZ()); + } + else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled()) + { + // If end world is generated, return end island bounding box. + //noinspection ConstantConditions + boundingBox = new BoundingBox(this.getMinX(), + this.getEndWorld().getMinHeight(), + this.getMinZ(), + this.getMaxX(), + this.getEndWorld().getMaxHeight(), + this.getMaxZ()); + } + else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled()) + { + // If nether world is generated, return nether island bounding box. + //noinspection ConstantConditions + boundingBox = new BoundingBox(this.getMinX(), + this.getNetherWorld().getMinHeight(), + this.getMinZ(), + this.getMaxX(), + this.getNetherWorld().getMaxHeight(), + this.getMaxZ()); + } + else + { + boundingBox = null; + } + + return boundingBox; + } + + + /** + * Using this method in the filtering for getVisitors and hasVisitors + * @param player The player that must be checked. + * @return true if player is a visitor + */ + private boolean playerIsVisitor(Player player) { + if (player.getGameMode() == GameMode.SPECTATOR) { + return false; + } + + return onIsland(player.getLocation()) && getRank(User.getInstance(player)) == RanksManager.VISITOR_RANK; + } /** * Returns a list of players that are physically inside the island's protection range and that are visitors. @@ -801,24 +910,83 @@ public class Island implements DataObject, MetaDataAble { * @param target location to check, not null * @return {@code true} if this location is within this island's protected area, {@code false} otherwise. */ + @SuppressWarnings("ConstantConditions") public boolean onIsland(@NonNull Location target) { - return Util.sameWorld(world, target.getWorld()) && target.getBlockX() >= getMinProtectedX() && target.getBlockX() < (getMinProtectedX() + protectionRange * 2) && target.getBlockZ() >= getMinProtectedZ() && target.getBlockZ() < (getMinProtectedZ() + protectionRange * 2); + return Util.sameWorld(this.world, target.getWorld()) && + (target.getWorld().getEnvironment().equals(Environment.NORMAL) || + this.getPlugin().getIWM().isIslandNether(target.getWorld()) || + this.getPlugin().getIWM().isIslandEnd(target.getWorld())) && + target.getBlockX() >= this.getMinProtectedX() && + target.getBlockX() < (this.getMinProtectedX() + this.protectionRange * 2) && + target.getBlockZ() >= this.getMinProtectedZ() && + target.getBlockZ() < (this.getMinProtectedZ() + this.protectionRange * 2); } /** - * Returns a {@link BoundingBox} of this island's protected area. + * Returns a {@link BoundingBox} of this island's protected area for overworld. * @return a {@link BoundingBox} of this island's protected area. * @since 1.5.2 */ + @SuppressWarnings("ConstantConditions") + @NotNull public BoundingBox getProtectionBoundingBox() { - return new BoundingBox(this.getMinProtectedX(), - this.world.getMinHeight(), - this.getMinProtectedZ(), - this.getMaxProtectedX(), - this.world.getMaxHeight(), - this.getMaxProtectedZ()); + return this.getProtectionBoundingBox(Environment.NORMAL); } + + /** + * Returns a {@link BoundingBox} of this island's protected area. + * @param environment an environment of bounding box area. + * @return a {@link BoundingBox} of this island's protected area or {@code null} if island is not created in required dimension. + * in required dimension. + * @since 1.21.0 + */ + @Nullable + public BoundingBox getProtectionBoundingBox(Environment environment) + { + BoundingBox boundingBox; + + if (Environment.NORMAL.equals(environment)) + { + // Return normal world bounding box. + boundingBox = new BoundingBox(this.getMinProtectedX(), + this.world.getMinHeight(), + this.getMinProtectedZ(), + this.getMaxProtectedX(), + this.world.getMaxHeight(), + this.getMaxProtectedZ()); + } + else if (Environment.THE_END.equals(environment) && this.isEndIslandEnabled()) + { + // If end world is generated, return end island bounding box. + //noinspection ConstantConditions + boundingBox = new BoundingBox(this.getMinProtectedX(), + this.getEndWorld().getMinHeight(), + this.getMinProtectedZ(), + this.getMaxProtectedX(), + this.getEndWorld().getMaxHeight(), + this.getMaxProtectedZ()); + } + else if (Environment.NETHER.equals(environment) && this.isNetherIslandEnabled()) + { + // If nether world is generated, return nether island bounding box. + //noinspection ConstantConditions + boundingBox = new BoundingBox(this.getMinProtectedX(), + this.getNetherWorld().getMinHeight(), + this.getMinProtectedZ(), + this.getMaxProtectedX(), + this.getNetherWorld().getMaxHeight(), + this.getMaxProtectedZ()); + } + else + { + boundingBox = null; + } + + return boundingBox; + } + + /** * Removes a player from the team member map. Generally, you should * use {@link world.bentobox.bentobox.managers.IslandsManager#removePlayer(World, UUID)} @@ -1258,6 +1426,15 @@ public class Island implements DataObject, MetaDataAble { return nether != null && !getCenter().toVector().toLocation(nether).getBlock().getType().isAir(); } + /** + * Checks whether this island has its nether island mode enabled or not. + * @return {@code true} if this island has its nether island enabled, {@code false} otherwise. + * @since 1.21.0 + */ + public boolean isNetherIslandEnabled() { + return this.getPlugin().getIWM().isNetherGenerate(this.world) && this.getPlugin().getIWM().isNetherIslands(this.world); + } + /** * Checks whether this island has its end island generated or not. * @return {@code true} if this island has its end island generated, {@code false} otherwise. @@ -1269,6 +1446,16 @@ public class Island implements DataObject, MetaDataAble { } + /** + * Checks whether this island has its end island mode enabled or not. + * @return {@code true} if this island has its end island enabled, {@code false} otherwise. + * @since 1.21.0 + */ + public boolean isEndIslandEnabled() { + return this.getPlugin().getIWM().isEndGenerate(this.world) && this.getPlugin().getIWM().isEndIslands(this.world); + } + + /** * Checks if a flag is on cooldown. Only stored in memory so a server restart will reset the cooldown. * @param flag - flag From c7b48b3d2a8d76505439a84e2ab3bbfe5b64a51a Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 26 Aug 2022 12:17:19 +0300 Subject: [PATCH 58/74] Update Spigot 1.19.2. (#2017) Add 1.19.2 compatibility. --- pom.xml | 7 ++++++- .../bentobox/bentobox/versions/ServerCompatibility.java | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2d98dd5bc..cf62879d3 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 2.0.9 3.12.8 - 1.19-R0.1-SNAPSHOT + 1.19.2-R0.1-SNAPSHOT 1.19-R0.1-SNAPSHOT @@ -171,6 +171,11 @@ minecraft-repo https://libraries.minecraft.net/ + + + nms-repo + https://repo.codemc.io/repository/nms/ + diff --git a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java index 8e534198d..bb5f140cd 100644 --- a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java +++ b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java @@ -206,6 +206,10 @@ public class ServerCompatibility { * @since 1.21.0 */ V1_19_1(Compatibility.COMPATIBLE), + /** + * @since 1.21.0 + */ + V1_19_2(Compatibility.COMPATIBLE), ; private final Compatibility compatibility; From 50bc236bbc6add029236b2b45a675897e04286e3 Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 26 Aug 2022 12:27:11 +0300 Subject: [PATCH 59/74] Fix failing unit-test --- .../world/bentobox/bentobox/database/objects/IslandTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java index 8e86e979d..fe0bf5c91 100644 --- a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java +++ b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java @@ -92,7 +92,8 @@ public class IslandTest { //when(location.getWorld()).thenReturn(world); when(location.clone()).thenReturn(location); when(world.getName()).thenReturn("bskyblock_world"); - + when(location.getWorld()).thenReturn(world); + when(world.getEnvironment()).thenReturn(Environment.NORMAL); // User when(user.getUniqueId()).thenReturn(uuid); From 9088ea4b800edaaaa5f29cbf34b2ec52d83567de Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 26 Aug 2022 12:43:05 +0300 Subject: [PATCH 60/74] Fix failing unit-test --- .../world/bentobox/bentobox/database/objects/IslandTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java index fe0bf5c91..a97232f2c 100644 --- a/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java +++ b/src/test/java/world/bentobox/bentobox/database/objects/IslandTest.java @@ -89,11 +89,12 @@ public class IslandTest { when(iwm.getIslandDistance(any())).thenReturn(DISTANCE); // Location - //when(location.getWorld()).thenReturn(world); when(location.clone()).thenReturn(location); when(world.getName()).thenReturn("bskyblock_world"); when(location.getWorld()).thenReturn(world); when(world.getEnvironment()).thenReturn(Environment.NORMAL); + when(world.toString()).thenReturn(null); + // User when(user.getUniqueId()).thenReturn(uuid); @@ -421,7 +422,7 @@ public class IslandTest { */ @Test public void testGetWorld() { - assertNull(i.getWorld()); + assertEquals(i.getWorld(), world); } /** From 12926f9ee7f9cfa1147fb2f77d9e95952af484b3 Mon Sep 17 00:00:00 2001 From: Huynh Tien Date: Mon, 5 Sep 2022 13:28:16 +0700 Subject: [PATCH 61/74] setBlock & setEntity as CompletableFuture (#2019) * setBlock & setEntity as CompletableFuture * use collectingAndThen toArray --- .../nms/fallback/PasteHandlerImpl.java | 21 +++++++++++++++---- .../bentobox/util/DefaultPasteUtil.java | 9 ++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/nms/fallback/PasteHandlerImpl.java b/src/main/java/world/bentobox/bentobox/nms/fallback/PasteHandlerImpl.java index 64e6df247..4ba2d8eef 100644 --- a/src/main/java/world/bentobox/bentobox/nms/fallback/PasteHandlerImpl.java +++ b/src/main/java/world/bentobox/bentobox/nms/fallback/PasteHandlerImpl.java @@ -11,17 +11,30 @@ import world.bentobox.bentobox.util.DefaultPasteUtil; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; public class PasteHandlerImpl implements PasteHandler { @Override public CompletableFuture pasteBlocks(Island island, World world, Map blockMap) { - blockMap.forEach((location, block) -> DefaultPasteUtil.setBlock(island, location, block)); - return CompletableFuture.completedFuture(null); + return blockMap.entrySet().parallelStream() + .map(entry -> DefaultPasteUtil.setBlock(island, entry.getKey(), entry.getValue())) + .collect( + Collectors.collectingAndThen( + Collectors.toList(), + list -> CompletableFuture.allOf(list.toArray(new CompletableFuture[0])) + ) + ); } @Override public CompletableFuture pasteEntities(Island island, World world, Map> entityMap) { - entityMap.forEach((location, blueprintEntities) -> DefaultPasteUtil.setEntity(island, location, blueprintEntities)); - return CompletableFuture.completedFuture(null); + return entityMap.entrySet().parallelStream() + .map(entry -> DefaultPasteUtil.setEntity(island, entry.getKey(), entry.getValue())) + .collect( + Collectors.collectingAndThen( + Collectors.toList(), + list -> CompletableFuture.allOf(list.toArray(new CompletableFuture[0])) + ) + ); } } diff --git a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java index cf1e4db33..f6e1ce18d 100644 --- a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java +++ b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java @@ -21,6 +21,7 @@ import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.nms.PasteHandler; import java.util.*; +import java.util.concurrent.CompletableFuture; /** * A utility class for {@link PasteHandler} @@ -43,8 +44,8 @@ public class DefaultPasteUtil { * @param location - location * @param bpBlock - blueprint block */ - public static void setBlock(Island island, Location location, BlueprintBlock bpBlock) { - Util.getChunkAtAsync(location).thenRun(() -> { + 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); @@ -152,10 +153,10 @@ public class DefaultPasteUtil { * @param location - location * @param list - blueprint entities */ - public static void setEntity(Island island, Location location, List list) { + public static CompletableFuture setEntity(Island island, Location location, List list) { World world = location.getWorld(); assert world != null; - Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null).forEach(k -> { + return Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null).forEach(k -> { LivingEntity e = (LivingEntity) location.getWorld().spawnEntity(location, k.getType()); if (k.getCustomName() != null) { String customName = k.getCustomName(); From 35ce1a7d81a45bb20448738f3823621b867f86ca Mon Sep 17 00:00:00 2001 From: BONNe Date: Thu, 29 Sep 2022 18:44:07 +0300 Subject: [PATCH 62/74] Rework blueprint and blueprint bundle names. (#2026) * Fixes issue when blueprint clipboard was stuck after saving. The issue was that Map#putIfAbsent still creates a new task, even if object is already in map. The usage requires to use Map#computeIfAbsent. * Rework blueprint and blueprint bundle names. There was an issue with using non-english characters in blueprint names. It was not possible, as all chars for names were converted to lower cased english letters. It included display names. I reworked it a bit and now it should be possible to set non-english names for bundles and blueprints. Fixes #1954 --- .../blueprints/AdminBlueprintCommand.java | 30 ++-- .../AdminBlueprintDeleteCommand.java | 58 +++++--- .../blueprints/AdminBlueprintListCommand.java | 47 +++--- .../blueprints/AdminBlueprintLoadCommand.java | 2 +- .../AdminBlueprintRenameCommand.java | 105 +++++++++----- .../blueprints/AdminBlueprintSaveCommand.java | 121 +++++++++++----- .../commands/island/IslandCreateCommand.java | 6 +- .../commands/island/IslandResetCommand.java | 6 +- .../bentobox/blueprints/Blueprint.java | 4 +- .../blueprints/conversation/NamePrompt.java | 81 ++++++----- .../conversation/NameSuccessPrompt.java | 72 ++++++---- .../dataobjects/BlueprintBundle.java | 2 +- .../managers/BlueprintClipboardManager.java | 31 ++-- .../bentobox/managers/BlueprintsManager.java | 134 ++++++++++-------- .../world/bentobox/bentobox/util/Util.java | 21 ++- src/main/resources/locales/en-US.yml | 6 +- .../BlueprintClipboardManagerTest.java | 23 +-- .../managers/BlueprintsManagerTest.java | 7 +- 18 files changed, 479 insertions(+), 277 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java index a869ed72a..ca8a6eaf0 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintCommand.java @@ -62,20 +62,30 @@ public class AdminBlueprintCommand extends ConfirmableCommand { return clipboards; } - protected void showClipboard(User user) { - displayClipboards.putIfAbsent(user, Bukkit.getScheduler().scheduleSyncRepeatingTask(getPlugin(), () -> { - if (!user.isPlayer() || !user.getPlayer().isOnline()) { - hideClipboard(user); - } - if (clipboards.containsKey(user.getUniqueId())) { - BlueprintClipboard clipboard = clipboards.get(user.getUniqueId()); - paintAxis(user, clipboard); - } + /** + * This method shows clipboard for requested user. + * @param user User who need to see clipboard. + */ + protected void showClipboard(User user) + { + this.displayClipboards.computeIfAbsent(user, + key -> Bukkit.getScheduler().scheduleSyncRepeatingTask(this.getPlugin(), () -> + { + if (!key.isPlayer() || !key.getPlayer().isOnline()) + { + this.hideClipboard(key); + } - }, 20, 20)); + if (this.clipboards.containsKey(key.getUniqueId())) + { + BlueprintClipboard clipboard = this.clipboards.get(key.getUniqueId()); + this.paintAxis(key, clipboard); + } + }, 20, 20)); } + private void paintAxis(User user, BlueprintClipboard clipboard) { if (clipboard.getPos1() == null || clipboard.getPos2() == null) { return; diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintDeleteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintDeleteCommand.java index 7b6de6df0..0a3615f77 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintDeleteCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintDeleteCommand.java @@ -8,49 +8,63 @@ import java.util.Optional; import world.bentobox.bentobox.api.commands.ConfirmableCommand; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.util.Util; + /** * Command that deletes a Blueprint. * @author Poslovitch * @since 1.9.0 */ -public class AdminBlueprintDeleteCommand extends ConfirmableCommand { - - public AdminBlueprintDeleteCommand(AdminBlueprintCommand parent) { +public class AdminBlueprintDeleteCommand extends ConfirmableCommand +{ + public AdminBlueprintDeleteCommand(AdminBlueprintCommand parent) + { super(parent, "delete", "remove"); } - @Override - public void setup() { - inheritPermission(); - setParametersHelp("commands.admin.blueprint.delete.parameters"); - setDescription("commands.admin.blueprint.delete.description"); - } @Override - public boolean execute(User user, String label, List args) { - if (args.size() != 1) { - showHelp(this, user); + public void setup() + { + this.inheritPermission(); + this.setParametersHelp("commands.admin.blueprint.delete.parameters"); + this.setDescription("commands.admin.blueprint.delete.description"); + } + + + @Override + public boolean execute(User user, String label, List args) + { + if (args.size() != 1) + { + this.showHelp(this, user); return false; } - String blueprintName = args.get(0).toLowerCase(Locale.ENGLISH); + String blueprintName = Util.sanitizeInput(args.get(0)); // Check if blueprint exist - if (getPlugin().getBlueprintsManager().getBlueprints(getAddon()).containsKey(blueprintName)) { - askConfirmation(user, user.getTranslation("commands.admin.blueprint.delete.confirmation"), () -> { - getPlugin().getBlueprintsManager().deleteBlueprint(getAddon(), blueprintName); - user.sendMessage("commands.admin.blueprint.delete.success", TextVariables.NAME, blueprintName); - }); + if (this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).containsKey(blueprintName)) + { + this.askConfirmation(user, user.getTranslation("commands.admin.blueprint.delete.confirmation"), + () -> { + this.getPlugin().getBlueprintsManager().deleteBlueprint(this.getAddon(), blueprintName); + user.sendMessage("commands.admin.blueprint.delete.success", TextVariables.NAME, blueprintName); + }); return true; - } else { + } + else + { user.sendMessage("commands.admin.blueprint.delete.no-blueprint", TextVariables.NAME, blueprintName); return false; } } + @Override - public Optional> tabComplete(User user, String alias, List args) { - return Optional.of(new LinkedList<>(getPlugin().getBlueprintsManager().getBlueprints(getAddon()).keySet())); + public Optional> tabComplete(User user, String alias, List args) + { + return Optional.of(new LinkedList<>(this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).keySet())); } -} +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintListCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintListCommand.java index 4435871be..57f19325a 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintListCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintListCommand.java @@ -5,51 +5,66 @@ import java.io.FilenameFilter; import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.managers.BlueprintsManager; -public class AdminBlueprintListCommand extends CompositeCommand { +public class AdminBlueprintListCommand extends CompositeCommand +{ - public AdminBlueprintListCommand(AdminBlueprintCommand parent) { + public AdminBlueprintListCommand(AdminBlueprintCommand parent) + { super(parent, "list"); } @Override - public void setup() { - inheritPermission(); - setDescription("commands.admin.blueprint.list.description"); + public void setup() + { + this.inheritPermission(); + this.setDescription("commands.admin.blueprint.list.description"); } + @Override - public boolean canExecute(User user, String label, List args) { - if (!args.isEmpty()) { - showHelp(this, user); + public boolean canExecute(User user, String label, List args) + { + if (!args.isEmpty()) + { + this.showHelp(this, user); return false; } + return true; } @Override - public boolean execute(User user, String label, List args) { - File blueprints = new File(getAddon().getDataFolder(), BlueprintsManager.FOLDER_NAME); - if (!blueprints.exists()) { + public boolean execute(User user, String label, List args) + { + File blueprints = new File(this.getAddon().getDataFolder(), BlueprintsManager.FOLDER_NAME); + + if (!blueprints.exists()) + { user.sendMessage("commands.admin.blueprint.list.no-blueprints"); return false; } - FilenameFilter blueprintFilter = (File dir, String name) -> name.toLowerCase(java.util.Locale.ENGLISH).endsWith(BlueprintsManager.BLUEPRINT_SUFFIX); - List blueprintList = Arrays.stream(Objects.requireNonNull(blueprints.list(blueprintFilter))).map(name -> name.substring(0, name.length() - BlueprintsManager.BLUEPRINT_SUFFIX.length())).collect(Collectors.toList()); - if (blueprintList.isEmpty()) { + + FilenameFilter blueprintFilter = (File dir, String name) -> name.endsWith(BlueprintsManager.BLUEPRINT_SUFFIX); + + List blueprintList = Arrays.stream(Objects.requireNonNull(blueprints.list(blueprintFilter))). + map(name -> name.substring(0, name.length() - BlueprintsManager.BLUEPRINT_SUFFIX.length())). + toList(); + + if (blueprintList.isEmpty()) + { user.sendMessage("commands.admin.blueprint.list.no-blueprints"); return false; } + user.sendMessage("commands.admin.blueprint.list.available-blueprints"); blueprintList.forEach(user::sendRawMessage); return true; } - } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommand.java index 72a264816..6bbef1502 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommand.java @@ -32,7 +32,7 @@ public class AdminBlueprintLoadCommand extends CompositeCommand { AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent(); BlueprintClipboardManager bp = new BlueprintClipboardManager(getPlugin(), parent.getBlueprintsFolder()); - if (bp.load(user, args.get(0))) { + if (bp.load(user, Util.sanitizeInput(args.get(0)))) { parent.getClipboards().put(user.getUniqueId(), bp.getClipboard()); return true; } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintRenameCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintRenameCommand.java index b1a21a959..f7863715d 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintRenameCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintRenameCommand.java @@ -8,66 +8,107 @@ import world.bentobox.bentobox.api.commands.ConfirmableCommand; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.blueprints.Blueprint; +import world.bentobox.bentobox.blueprints.BlueprintClipboard; import world.bentobox.bentobox.managers.BlueprintsManager; +import world.bentobox.bentobox.util.Util; + /** * Renames an existing blueprint. * @author Poslovitch * @since 1.10.0 */ -public class AdminBlueprintRenameCommand extends ConfirmableCommand { - - public AdminBlueprintRenameCommand(AdminBlueprintCommand parent) { +public class AdminBlueprintRenameCommand extends ConfirmableCommand +{ + public AdminBlueprintRenameCommand(AdminBlueprintCommand parent) + { super(parent, "rename"); } - @Override - public void setup() { - inheritPermission(); - setParametersHelp("commands.admin.blueprint.rename.parameters"); - setDescription("commands.admin.blueprint.rename.description"); - } @Override - public boolean execute(User user, String label, List args) { - if (args.size() != 2) { - showHelp(this, user); + public void setup() + { + this.inheritPermission(); + this.setParametersHelp("commands.admin.blueprint.rename.parameters"); + this.setDescription("commands.admin.blueprint.rename.description"); + } + + + @Override + public boolean canExecute(User user, String label, List args) + { + if (args.size() != 2) + { + // Blueprint must have a name. + this.showHelp(this, user); return false; } - AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent(); + String from = Util.sanitizeInput(args.get(0)); + String to = Util.sanitizeInput(args.get(1)); - // Check if the names are the same - String from = args.get(0).toLowerCase(Locale.ENGLISH); - String to = args.get(1).toLowerCase(Locale.ENGLISH); - - if (from.equals(to)) { + // Check if name is changed. + if (from.equals(to)) + { user.sendMessage("commands.admin.blueprint.rename.pick-different-name"); return false; } // Check if the 'from' file exists + AdminBlueprintCommand parent = (AdminBlueprintCommand) this.getParent(); File fromFile = new File(parent.getBlueprintsFolder(), from + BlueprintsManager.BLUEPRINT_SUFFIX); - if (!fromFile.exists()) { + + if (!fromFile.exists()) + { user.sendMessage("commands.admin.blueprint.no-such-file"); return false; } - // Check if the 'to' file exists - - File toFile = new File(parent.getBlueprintsFolder(), to + BlueprintsManager.BLUEPRINT_SUFFIX); - if (toFile.exists()) { - // Ask for confirmation to overwrite - askConfirmation(user, user.getTranslation("commands.admin.blueprint.file-exists"), () -> rename(user, from, to)); - } else { - askConfirmation(user, () -> rename(user, from, to)); - } return true; } - private void rename(User user, String blueprintName, String newName) { - Blueprint blueprint = getPlugin().getBlueprintsManager().getBlueprints(getAddon()).get(blueprintName); - getPlugin().getBlueprintsManager().renameBlueprint(getAddon(), blueprint, newName); - user.sendMessage("commands.admin.blueprint.rename.success", "[old]", blueprintName, TextVariables.NAME, blueprint.getName()); + + @Override + public boolean execute(User user, String label, List args) + { + AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent(); + + // Check if the names are the same + String from = Util.sanitizeInput(args.get(0)); + String to = Util.sanitizeInput(args.get(1)); + + // Check if the 'to' file exists + File toFile = new File(parent.getBlueprintsFolder(), to + BlueprintsManager.BLUEPRINT_SUFFIX); + + if (toFile.exists()) + { + // Ask for confirmation to overwrite + this.askConfirmation(user, + user.getTranslation("commands.admin.blueprint.file-exists"), + () -> this.rename(user, from, to, args.get(1))); + } + else + { + this.askConfirmation(user, () -> this.rename(user, from, to, args.get(1))); + } + + return true; + } + + + private void rename(User user, String blueprintName, String fileName, String displayName) + { + Blueprint blueprint = this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).get(blueprintName); + + this.getPlugin().getBlueprintsManager().renameBlueprint(this.getAddon(), blueprint, fileName, displayName); + + user.sendMessage("commands.admin.blueprint.rename.success", + "[old]", + blueprintName, + TextVariables.NAME, + blueprint.getName(), + "[display]", + blueprint.getDisplayName()); } } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java index 7ec6af2ba..b0b957171 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java @@ -2,62 +2,115 @@ package world.bentobox.bentobox.api.commands.admin.blueprints; import java.io.File; import java.util.List; -import java.util.Locale; import world.bentobox.bentobox.api.commands.ConfirmableCommand; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.blueprints.BlueprintClipboard; import world.bentobox.bentobox.managers.BlueprintClipboardManager; import world.bentobox.bentobox.managers.BlueprintsManager; +import world.bentobox.bentobox.util.Util; -public class AdminBlueprintSaveCommand extends ConfirmableCommand { - public AdminBlueprintSaveCommand(AdminBlueprintCommand parent) { +/** + * This method allows to save blueprint from the clipboard. + */ +public class AdminBlueprintSaveCommand extends ConfirmableCommand +{ + public AdminBlueprintSaveCommand(AdminBlueprintCommand parent) + { super(parent, "save"); } - @Override - public void setup() { - inheritPermission(); - setParametersHelp("commands.admin.blueprint.save.parameters"); - setDescription("commands.admin.blueprint.save.description"); - } @Override - public boolean execute(User user, String label, List args) { - if (args.size() != 1) { - showHelp(this, user); + public void setup() + { + this.inheritPermission(); + this.setParametersHelp("commands.admin.blueprint.save.parameters"); + this.setDescription("commands.admin.blueprint.save.description"); + } + + + @Override + public boolean canExecute(User user, String label, List args) + { + if (args.size() != 1) + { + // Blueprint must have a name. + this.showHelp(this, user); return false; } - AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent(); - BlueprintClipboard clipboard = parent.getClipboards().computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard()); - String fileName = args.get(0).toLowerCase(Locale.ENGLISH); - if (clipboard.isFull()) { - // Check if blueprint had bedrock - if (clipboard.getBlueprint().getBedrock() == null) { - user.sendMessage("commands.admin.blueprint.bedrock-required"); - return false; - } - // Check if file exists - File newFile = new File(parent.getBlueprintsFolder(), fileName + BlueprintsManager.BLUEPRINT_SUFFIX); - if (newFile.exists()) { - this.askConfirmation(user, user.getTranslation("commands.admin.blueprint.file-exists"), () -> hideAndSave(user, parent, clipboard, fileName)); - return false; - } - return hideAndSave(user, parent, clipboard, fileName); - } else { + BlueprintClipboard clipboard = ((AdminBlueprintCommand) this.getParent()).getClipboards(). + computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard()); + + if (!clipboard.isFull()) + { + // Clipboard is not set up. user.sendMessage("commands.admin.blueprint.copy-first"); return false; } + + if (clipboard.getBlueprint().getBedrock() == null) + { + // Bedrock is required for all blueprints. + user.sendMessage("commands.admin.blueprint.bedrock-required"); + return false; + } + + return true; } - private boolean hideAndSave(User user, AdminBlueprintCommand parent, BlueprintClipboard clipboard, String name) { - parent.hideClipboard(user); - boolean result = new BlueprintClipboardManager(getPlugin(), parent.getBlueprintsFolder(), clipboard).save(user, name); - if (result && clipboard.getBlueprint() != null) { - getPlugin().getBlueprintsManager().addBlueprint(getAddon(), clipboard.getBlueprint()); + + @Override + public boolean execute(User user, String label, List args) + { + AdminBlueprintCommand parent = (AdminBlueprintCommand) this.getParent(); + BlueprintClipboard clipboard = parent.getClipboards(). + computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard()); + + String fileName = Util.sanitizeInput(args.get(0)); + + // Check if file exists + File newFile = new File(parent.getBlueprintsFolder(), fileName + BlueprintsManager.BLUEPRINT_SUFFIX); + + if (newFile.exists()) + { + this.askConfirmation(user, + user.getTranslation("commands.admin.blueprint.file-exists"), + () -> this.hideAndSave(user, parent, clipboard, fileName, args.get(0))); + return false; } + + return this.hideAndSave(user, parent, clipboard, fileName, args.get(0)); + } + + + /** + * This method saves given blueprint. + * @param user User that triggers blueprint save. + * @param parent Parent command that contains clipboard. + * @param clipboard Active clipboard. + * @param name Filename for the blueprint + * @param displayName Display name for the blueprint. + * @return {@code true} if blueprint is saved, {@code false} otherwise. + */ + private boolean hideAndSave(User user, + AdminBlueprintCommand parent, + BlueprintClipboard clipboard, + String name, + String displayName) + { + parent.hideClipboard(user); + boolean result = new BlueprintClipboardManager(this.getPlugin(), + parent.getBlueprintsFolder(), clipboard). + save(user, name, displayName); + + if (result && clipboard.isFull()) + { + this.getPlugin().getBlueprintsManager().addBlueprint(this.getAddon(), clipboard.getBlueprint()); + } + return result; } } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandCreateCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandCreateCommand.java index 6e2d21d8b..5426b764b 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandCreateCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandCreateCommand.java @@ -12,6 +12,8 @@ import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.managers.BlueprintsManager; import world.bentobox.bentobox.managers.island.NewIsland; import world.bentobox.bentobox.panels.IslandCreationPanel; +import world.bentobox.bentobox.util.Util; + /** * /island create - Create an island. @@ -63,13 +65,13 @@ public class IslandCreateCommand extends CompositeCommand { public boolean execute(User user, String label, List args) { // Permission check if the name is not the default one if (!args.isEmpty()) { - String name = getPlugin().getBlueprintsManager().validate(getAddon(), args.get(0).toLowerCase(java.util.Locale.ENGLISH)); + String name = getPlugin().getBlueprintsManager().validate(getAddon(), Util.sanitizeInput(args.get(0))); if (name == null) { // The blueprint name is not valid. user.sendMessage("commands.island.create.unknown-blueprint"); return false; } - if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, args.get(0))) { + if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, Util.sanitizeInput(args.get(0)))) { return false; } // Make island diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java index 6e059af6b..f6c60aa83 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java @@ -17,6 +17,8 @@ import world.bentobox.bentobox.managers.BlueprintsManager; import world.bentobox.bentobox.managers.island.NewIsland; import world.bentobox.bentobox.managers.island.NewIsland.Builder; import world.bentobox.bentobox.panels.IslandCreationPanel; +import world.bentobox.bentobox.util.Util; + /** * @author tastybento @@ -78,13 +80,13 @@ public class IslandResetCommand extends ConfirmableCommand { public boolean execute(User user, String label, List args) { // Permission check if the name is not the default one if (!args.isEmpty()) { - String name = getPlugin().getBlueprintsManager().validate(getAddon(), args.get(0).toLowerCase(java.util.Locale.ENGLISH)); + String name = getPlugin().getBlueprintsManager().validate(getAddon(), Util.sanitizeInput(args.get(0))); if (name == null || name.isEmpty()) { // The blueprint name is not valid. user.sendMessage("commands.island.create.unknown-blueprint"); return false; } - if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, args.get(0))) { + if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, Util.sanitizeInput(args.get(0)))) { return false; } return resetIsland(user, name); diff --git a/src/main/java/world/bentobox/bentobox/blueprints/Blueprint.java b/src/main/java/world/bentobox/bentobox/blueprints/Blueprint.java index 38d73eec8..da3304b59 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/Blueprint.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/Blueprint.java @@ -53,14 +53,14 @@ public class Blueprint { public String getName() { if (name == null) name = "unnamed"; // Force lower case - return name.toLowerCase(Locale.ENGLISH); + return name; } /** * @param name the name to set */ public Blueprint setName(@NonNull String name) { // Force lowercase - this.name = name.toLowerCase(Locale.ENGLISH); + this.name = name; return this; } /** diff --git a/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java b/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java index 25b86506e..c74f288b7 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/conversation/NamePrompt.java @@ -1,7 +1,5 @@ package world.bentobox.bentobox.blueprints.conversation; -import java.util.Locale; - import org.bukkit.conversations.ConversationContext; import org.bukkit.conversations.Prompt; import org.bukkit.conversations.StringPrompt; @@ -18,67 +16,74 @@ import world.bentobox.bentobox.managers.BlueprintsManager; import world.bentobox.bentobox.util.Util; -public class NamePrompt extends StringPrompt { - +public class NamePrompt extends StringPrompt +{ private final GameModeAddon addon; + @Nullable private final BlueprintBundle bb; + @Nullable private Blueprint bp; - public NamePrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb) { + + public NamePrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb) + { this.addon = addon; this.bb = bb; } - public NamePrompt(@NonNull GameModeAddon addon, @Nullable Blueprint bp, @Nullable BlueprintBundle bb) { + + public NamePrompt(@NonNull GameModeAddon addon, @Nullable Blueprint bp, @Nullable BlueprintBundle bb) + { this.addon = addon; this.bp = bp; this.bb = bb; } + @Override - public @NonNull String getPromptText(ConversationContext context) { - User user = User.getInstance((Player)context.getForWhom()); + public @NonNull String getPromptText(ConversationContext context) + { + User user = User.getInstance((Player) context.getForWhom()); return user.getTranslation("commands.admin.blueprint.management.name.prompt"); } - @Override - public Prompt acceptInput(ConversationContext context, String input) { - User user = User.getInstance((Player)context.getForWhom()); - // Convert color codes - input = Util.translateColorCodes(input); - if (ChatColor.stripColor(input).length() > 32) { - context.getForWhom().sendRawMessage("Too long"); - return this; - /*Check if unique name contains chars not supported in regex expression - Cannot start, contain, or end with special char, cannot contain any numbers. - Can only contain - for word separation*/ - }else if (!ChatColor.stripColor(input).matches("^[a-zA-Z]+(?:-[a-zA-Z]+)*$")) { - context.getForWhom().sendRawMessage(user.getTranslation("commands.admin.blueprint.management.name.invalid-char-in-unique-name")); + @Override + public Prompt acceptInput(ConversationContext context, String input) + { + User user = User.getInstance((Player) context.getForWhom()); + String uniqueId = Util.sanitizeInput(input); + + // Convert color codes + if (ChatColor.stripColor(Util.translateColorCodes(input)).length() > 32) + { + context.getForWhom().sendRawMessage( + user.getTranslation("commands.admin.blueprint.management.name.too-long")); return this; } - if (bb == null || !bb.getUniqueId().equals(BlueprintsManager.DEFAULT_BUNDLE_NAME)) { - // Make a uniqueid - StringBuilder uniqueId = new StringBuilder(ChatColor.stripColor(input).toLowerCase(Locale.ENGLISH).replace(" ", "_")); + + if (this.bb == null || !this.bb.getUniqueId().equals(BlueprintsManager.DEFAULT_BUNDLE_NAME)) + { // Check if this name is unique - int max = 0; - while (max++ < 32 && addon.getPlugin().getBlueprintsManager().getBlueprintBundles(addon).containsKey(uniqueId.toString())) { - uniqueId.append("x"); - } - if (max == 32) { - context.getForWhom().sendRawMessage(user.getTranslation("commands.admin.blueprint.management.name.pick-a-unique-name")); + if (this.addon.getPlugin().getBlueprintsManager().getBlueprintBundles(this.addon).containsKey(uniqueId)) + { + context.getForWhom().sendRawMessage( + user.getTranslation("commands.admin.blueprint.management.name.pick-a-unique-name")); return this; } - context.setSessionData("uniqueId", uniqueId.toString()); - } else { - // Default stays as default - context.setSessionData("uniqueId", bb.getUniqueId()); + + context.setSessionData("uniqueId", uniqueId); } + else + { + // Default stays as default + context.setSessionData("uniqueId", this.bb.getUniqueId()); + } + context.setSessionData("name", input); - return new NameSuccessPrompt(addon, bb, bp); + + return new NameSuccessPrompt(this.addon, this.bb, this.bp); } - -} - +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/blueprints/conversation/NameSuccessPrompt.java b/src/main/java/world/bentobox/bentobox/blueprints/conversation/NameSuccessPrompt.java index 9e990fefe..2b2c6a4e3 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/conversation/NameSuccessPrompt.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/conversation/NameSuccessPrompt.java @@ -15,58 +15,76 @@ import world.bentobox.bentobox.blueprints.Blueprint; import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBundle; import world.bentobox.bentobox.panels.BlueprintManagementPanel; -public class NameSuccessPrompt extends MessagePrompt { +public class NameSuccessPrompt extends MessagePrompt +{ private final GameModeAddon addon; + private BlueprintBundle bb; + private final Blueprint bp; + /** * Handles the name processing + * * @param addon - Game Mode addon * @param bb - Blueprint Bundle * @param bp - blueprint */ - public NameSuccessPrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb, @Nullable Blueprint bp) { + public NameSuccessPrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb, @Nullable Blueprint bp) + { this.addon = addon; this.bb = bb; this.bp = bp; } + @Override - public @NonNull String getPromptText(ConversationContext context) { + public @NonNull String getPromptText(ConversationContext context) + { String name = (String) context.getSessionData("name"); String uniqueId = (String) context.getSessionData("uniqueId"); - User user = User.getInstance((Player)context.getForWhom()); - // Rename blueprint - if (bp != null) { - BentoBox.getInstance().getBlueprintsManager().renameBlueprint(addon, bp, name); - new BlueprintManagementPanel(BentoBox.getInstance(), user, addon).openBB(bb); - } else { - // Blueprint Bundle - if (bb == null) { - // New Blueprint bundle - bb = new BlueprintBundle(); - bb.setIcon(Material.RED_WOOL); - } else { - // Rename - remove old named file - BentoBox.getInstance().getBlueprintsManager().deleteBlueprintBundle(addon, bb); - } - bb.setDisplayName(name); - bb.setUniqueId(uniqueId); - BentoBox.getInstance().getBlueprintsManager().addBlueprintBundle(addon, bb); - BentoBox.getInstance().getBlueprintsManager().saveBlueprintBundle(addon, bb); - new BlueprintManagementPanel(BentoBox.getInstance(), user, addon).openPanel(); - // Set the name - // if successfully + User user = User.getInstance((Player) context.getForWhom()); + + // Rename blueprint + if (this.bp != null) + { + this.addon.getPlugin().getBlueprintsManager().renameBlueprint(this.addon, this.bp, uniqueId, name); + new BlueprintManagementPanel(this.addon.getPlugin(), user, this.addon).openBB(this.bb); } + else + { + // Blueprint Bundle + if (this.bb == null) + { + // New Blueprint bundle + this.bb = new BlueprintBundle(); + this.bb.setIcon(Material.RED_WOOL); + } + else + { + // Rename - remove old named file + this.addon.getPlugin().getBlueprintsManager().deleteBlueprintBundle(this.addon, this.bb); + } + + this.bb.setDisplayName(name); + this.bb.setUniqueId(uniqueId); + this.addon.getPlugin().getBlueprintsManager().addBlueprintBundle(this.addon, this.bb); + this.addon.getPlugin().getBlueprintsManager().saveBlueprintBundle(this.addon, this.bb); + + new BlueprintManagementPanel(this.addon.getPlugin(), user, this.addon).openPanel(); + // Set the name + } + return user.getTranslation("commands.admin.blueprint.management.description.success"); } + @Override - protected Prompt getNextPrompt(@NonNull ConversationContext context) { + protected Prompt getNextPrompt(@NonNull ConversationContext context) + { return Prompt.END_OF_CONVERSATION; } - } \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintBundle.java b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintBundle.java index 19384d3fd..59f457c28 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintBundle.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintBundle.java @@ -69,7 +69,7 @@ public class BlueprintBundle implements DataObject { */ @Override public String getUniqueId() { - return uniqueId.toLowerCase(Locale.ENGLISH); + return uniqueId; } /** * @param uniqueId the uniqueId to set diff --git a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java index 60e589a3c..8afb589fc 100644 --- a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java @@ -7,6 +7,7 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -86,24 +87,24 @@ public class BlueprintClipboardManager { /** * Loads a blueprint - * @param fileName - the filename without the suffix + * @param fileName - the sanitized filename without the suffix * @return the blueprint * @throws IOException exception if there's an issue loading or unzipping */ public Blueprint loadBlueprint(String fileName) throws IOException { - File zipFile = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX); + File zipFile = new File(blueprintFolder, fileName + BlueprintsManager.BLUEPRINT_SUFFIX); if (!zipFile.exists()) { plugin.logError(LOAD_ERROR + zipFile.getName()); throw new IOException(LOAD_ERROR + zipFile.getName()); } unzip(zipFile.getCanonicalPath()); - File file = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(fileName)); + File file = new File(blueprintFolder, fileName); if (!file.exists()) { plugin.logError(LOAD_ERROR + file.getName()); throw new IOException(LOAD_ERROR + file.getName() + " temp file"); } Blueprint bp; - try (FileReader fr = new FileReader(file)) { + try (FileReader fr = new FileReader(file, StandardCharsets.UTF_8)) { bp = gson.fromJson(fr, Blueprint.class); } catch (Exception e) { plugin.logError("Blueprint has JSON error: " + zipFile.getName()); @@ -114,7 +115,7 @@ public class BlueprintClipboardManager { if (bp.getBedrock() == null) { bp.setBedrock(new Vector(bp.getxSize() / 2, bp.getySize() / 2, bp.getzSize() / 2)); bp.getBlocks().put(bp.getBedrock(), new BlueprintBlock(Material.BEDROCK.createBlockData().getAsString())); - plugin.logWarning("Blueprint " + BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX + " had no bedrock block in it so one was added automatically in the center. You should check it."); + plugin.logWarning("Blueprint " + fileName + BlueprintsManager.BLUEPRINT_SUFFIX + " had no bedrock block in it so one was added automatically in the center. You should check it."); } return bp; } @@ -130,7 +131,7 @@ public class BlueprintClipboardManager { load(fileName); } catch (IOException e1) { user.sendMessage("commands.admin.blueprint.could-not-load"); - plugin.logError("Could not load blueprint file: " + BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX + " " + e1.getMessage()); + plugin.logError("Could not load blueprint file: " + fileName + BlueprintsManager.BLUEPRINT_SUFFIX + " " + e1.getMessage()); return false; } user.sendMessage("general.success"); @@ -143,14 +144,20 @@ public class BlueprintClipboardManager { * @param newName - new name of this blueprint * @return - true if successful, false if error */ - public boolean save(User user, String newName) { - if (clipboard.getBlueprint() != null) { - clipboard.getBlueprint().setName(newName); - if (saveBlueprint(clipboard.getBlueprint())) { + public boolean save(User user, String newName, String displayName) + { + if (this.clipboard.isFull()) + { + this.clipboard.getBlueprint().setName(newName); + this.clipboard.getBlueprint().setDisplayName(displayName); + + if (this.saveBlueprint(this.clipboard.getBlueprint())) + { user.sendMessage("general.success"); return true; } } + user.sendMessage("commands.admin.blueprint.could-not-save", "[message]", "Could not save temp blueprint file."); return false; } @@ -165,9 +172,9 @@ public class BlueprintClipboardManager { plugin.logError("Blueprint name was empty - could not save it"); return false; } - File file = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(blueprint.getName())); + File file = new File(blueprintFolder, blueprint.getName()); String toStore = gson.toJson(blueprint, Blueprint.class); - try (FileWriter fileWriter = new FileWriter(file)) { + try (FileWriter fileWriter = new FileWriter(file, StandardCharsets.UTF_8)) { fileWriter.write(toStore); } catch (IOException e) { plugin.logError("Could not save temporary blueprint file: " + file.getName()); diff --git a/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java b/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java index 9ed648df3..ea06a903f 100644 --- a/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/BlueprintsManager.java @@ -1,11 +1,9 @@ package world.bentobox.bentobox.managers; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; +import java.io.*; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -207,13 +205,24 @@ public class BlueprintsManager { bpf.mkdirs(); } boolean loaded = false; - File[] bundles = bpf.listFiles((dir, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(BLUEPRINT_BUNDLE_SUFFIX)); + File[] bundles = bpf.listFiles((dir, name) -> name.endsWith(BLUEPRINT_BUNDLE_SUFFIX)); + if (bundles == null || bundles.length == 0) { return false; } + for (File file : bundles) { - try { - BlueprintBundle bb = gson.fromJson(new FileReader(file), BlueprintBundle.class); + + try (FileReader fileReader = new FileReader(file, StandardCharsets.UTF_8)) + { + if (!file.getName().equals(Util.sanitizeInput(file.getName()))) + { + // fail on all blueprints with incorrect names. + throw new InputMismatchException(file.getName()); + } + + BlueprintBundle bb = gson.fromJson(fileReader, BlueprintBundle.class); + if (bb != null) { // Make sure there is no existing bundle with the same uniqueId if (blueprintBundles.get(addon).stream().noneMatch(bundle -> bundle.getUniqueId().equals(bb.getUniqueId()))) { @@ -290,13 +299,17 @@ public class BlueprintsManager { plugin.logError("There is no blueprint folder for addon " + addon.getDescription().getName()); bpf.mkdirs(); } - File[] bps = bpf.listFiles((dir, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(BLUEPRINT_SUFFIX)); + File[] bps = bpf.listFiles((dir, name) -> name.endsWith(BLUEPRINT_SUFFIX)); + if (bps == null || bps.length == 0) { plugin.logError("No blueprints found for " + addon.getDescription().getName()); return; } for (File file : bps) { - String fileName = file.getName().substring(0, file.getName().length() - BLUEPRINT_SUFFIX.length()); + + // Input sanitization is required for weirdos that edit files manually. + String fileName = Util.sanitizeInput(file.getName().substring(0, file.getName().length() - BLUEPRINT_SUFFIX.length())); + try { Blueprint bp = new BlueprintClipboardManager(plugin, bpf).loadBlueprint(fileName); bp.setName(fileName); @@ -345,9 +358,9 @@ public class BlueprintsManager { if (!bpf.exists()) { bpf.mkdirs(); } - File fileName = new File(bpf, sanitizeFileName(bb.getUniqueId()) + BLUEPRINT_BUNDLE_SUFFIX); + File fileName = new File(bpf, bb.getUniqueId() + BLUEPRINT_BUNDLE_SUFFIX); String toStore = gson.toJson(bb, BlueprintBundle.class); - try (FileWriter fileWriter = new FileWriter(fileName)) { + try (FileWriter fileWriter = new FileWriter(fileName, StandardCharsets.UTF_8)) { fileWriter.write(toStore); } catch (IOException e) { plugin.logError("Could not save blueprint bundle file: " + e.getMessage()); @@ -355,20 +368,6 @@ public class BlueprintsManager { }); } - /** - * Sanitizes a filename as much as possible retaining the original name - * @param name - filename to sanitize - * @return sanitized name - */ - public static String sanitizeFileName(String name) { - return name - .chars() - .mapToObj(i -> (char) i) - .map(c -> Character.isWhitespace(c) ? '_' : c) - .filter(c -> Character.isLetterOrDigit(c) || c == '-' || c == '_') - .map(String::valueOf) - .collect(Collectors.joining()); - } /** * Saves all the blueprint bundles @@ -396,26 +395,35 @@ public class BlueprintsManager { * @param name name of the Blueprint to delete * @since 1.9.0 */ - public void deleteBlueprint(GameModeAddon addon, String name) { - List addonBlueprints = blueprints.get(addon); + public void deleteBlueprint(GameModeAddon addon, String name) + { + List addonBlueprints = this.blueprints.get(addon); Iterator it = addonBlueprints.iterator(); - while (it.hasNext()) { - Blueprint b = it.next(); - if (b.getName().equalsIgnoreCase(name)) { - it.remove(); - blueprints.put(addon, addonBlueprints); - File file = new File(getBlueprintsFolder(addon), b.getName() + BLUEPRINT_SUFFIX); + while (it.hasNext()) + { + Blueprint b = it.next(); + + if (b.getName().equalsIgnoreCase(name)) + { + it.remove(); + + File file = new File(this.getBlueprintsFolder(addon), b.getName() + BLUEPRINT_SUFFIX); + // Delete the file - try { + try + { Files.deleteIfExists(file.toPath()); - } catch (IOException e) { - plugin.logError("Could not delete Blueprint " + e.getLocalizedMessage()); + } + catch (IOException e) + { + this.plugin.logError("Could not delete Blueprint " + e.getLocalizedMessage()); } } } } + /** * Paste the islands to world * @@ -441,7 +449,7 @@ public class BlueprintsManager { plugin.logError("Tried to paste '" + name + "' but the bundle is not loaded!"); return false; } - BlueprintBundle bb = getBlueprintBundles(addon).get(name.toLowerCase(Locale.ENGLISH)); + BlueprintBundle bb = getBlueprintBundles(addon).get(name.toLowerCase()); if (!blueprints.containsKey(addon) || blueprints.get(addon).isEmpty()) { plugin.logError("No blueprints loaded for bundle '" + name + "'!"); return false; @@ -514,7 +522,7 @@ public class BlueprintsManager { if (name == null) { return null; } - if (blueprintBundles.containsKey(addon) && getBlueprintBundles(addon).containsKey(name.toLowerCase(Locale.ENGLISH))) { + if (blueprintBundles.containsKey(addon) && getBlueprintBundles(addon).containsKey(name)) { return name; } return null; @@ -546,7 +554,7 @@ public class BlueprintsManager { // Permission String permission = addon.getPermissionPrefix() + "island.create." + name; // Get Blueprint bundle - BlueprintBundle bb = getBlueprintBundles((GameModeAddon) addon).get(name.toLowerCase(Locale.ENGLISH)); + BlueprintBundle bb = getBlueprintBundles((GameModeAddon) addon).get(name.toLowerCase()); if (bb == null || (bb.isRequirePermission() && !name.equals(DEFAULT_BUNDLE_NAME) && !user.hasPermission(permission))) { user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, permission); return false; @@ -565,7 +573,7 @@ public class BlueprintsManager { blueprintBundles.get(addon).removeIf(k -> k.getUniqueId().equals(bb.getUniqueId())); } File bpf = getBlueprintsFolder(addon); - File fileName = new File(bpf, sanitizeFileName(bb.getUniqueId()) + BLUEPRINT_BUNDLE_SUFFIX); + File fileName = new File(bpf, bb.getUniqueId() + BLUEPRINT_BUNDLE_SUFFIX); try { Files.deleteIfExists(fileName.toPath()); } catch (IOException e) { @@ -579,25 +587,39 @@ public class BlueprintsManager { * @param addon - Game Mode Addon * @param bp - blueprint * @param name - new name + * @param displayName - display name for blueprint */ - public void renameBlueprint(GameModeAddon addon, Blueprint bp, String name) { - if (bp.getName().equalsIgnoreCase(name)) { + public void renameBlueprint(GameModeAddon addon, Blueprint bp, String name, String displayName) + { + if (bp.getName().equalsIgnoreCase(name)) + { // If the name is the same, do not do anything return; } - File bpf = getBlueprintsFolder(addon); - // Get the filename - File fileName = new File(bpf, sanitizeFileName(bp.getName()) + BLUEPRINT_SUFFIX); - // Delete the old file - try { - Files.deleteIfExists(fileName.toPath()); - } catch (IOException e) { - plugin.logError("Could not delete old Blueprint " + e.getLocalizedMessage()); - } - // Set new name - bp.setName(name.toLowerCase(Locale.ENGLISH)); - // Save it - saveBlueprint(addon, bp); - } + File bpf = this.getBlueprintsFolder(addon); + // Get the filename + File fileName = new File(bpf, bp.getName() + BLUEPRINT_SUFFIX); + // Delete the old file + + try + { + Files.deleteIfExists(fileName.toPath()); + } + catch (IOException e) + { + this.plugin.logError("Could not delete old Blueprint " + e.getLocalizedMessage()); + } + + // Remove blueprint from the blueprints. + this.blueprints.get(addon).remove(bp); + + // Set new name + bp.setName(name); + bp.setDisplayName(displayName); + + // Save it + this.saveBlueprint(addon, bp); + this.addBlueprint(addon, bp); + } } diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java index 6b4184aa0..ad56f9384 100644 --- a/src/main/java/world/bentobox/bentobox/util/Util.java +++ b/src/main/java/world/bentobox/bentobox/util/Util.java @@ -2,11 +2,7 @@ package world.bentobox.bentobox.util; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.Enumeration; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -761,4 +757,19 @@ public class Util { } return count; } + + + /** + * This method removes all special characters that are not allowed in filenames (windows). + * It also includes any white-spaces, as for some reason, I do like it more without them. + * Also, all cases are lower cased for easier blueprint mapping. + * @param input Input that need to be sanitized. + * @return A sanitized input without illegal characters in names. + */ + public static String sanitizeInput(String input) + { + return ChatColor.stripColor( + Util.translateColorCodes(input.replaceAll("[\\\\/:*?\"<>|\s]", "_"))). + toLowerCase(); + } } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 1616bfe77..0dbc062cd 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -332,7 +332,7 @@ commands: rename: parameters: " " description: "rename a blueprint" - success: "&a Blueprint &b [old] &a has been successfully renamed to &b [name]&a." + success: "&a Blueprint &b [old] &a has been successfully renamed to &b [display]&a. Filename now is &b [name]&a." pick-different-name: "&c Please specify a name that is different from the blueprint's current name." management: back: "Back" @@ -366,9 +366,9 @@ commands: name: quit: "quit" prompt: "Enter a name, or 'quit' to quit" - too-long: "&c Too long" + too-long: "&c Too long name. Only 32 chars are allowed." pick-a-unique-name: "Please pick a more unique name" - invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! " + stripped-char-in-unique-name: "&c Some chars were removed because they are not allowed. &a New ID will be &b [name]&a." success: "Success!" conversation-prefix: ">" description: diff --git a/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java index 190daa323..4e0919ff8 100644 --- a/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java @@ -39,6 +39,8 @@ import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.blueprints.Blueprint; import world.bentobox.bentobox.blueprints.BlueprintClipboard; +import world.bentobox.bentobox.util.Util; + /** * @author tastybento @@ -345,7 +347,7 @@ public class BlueprintClipboardManagerTest { } /** - * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}. * @throws IOException */ @Test @@ -361,14 +363,14 @@ public class BlueprintClipboardManagerTest { BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); bcm.load(BLUEPRINT); User user = mock(User.class); - assertTrue(bcm.save(user, "test1234")); + assertTrue(bcm.save(user, "test1234", "")); File bp = new File(blueprintFolder, "test1234.blu"); assertTrue(bp.exists()); verify(user).sendMessage("general.success"); } /** - * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}. * @throws IOException */ @Test @@ -384,14 +386,14 @@ public class BlueprintClipboardManagerTest { BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); bcm.load(BLUEPRINT); User user = mock(User.class); - assertTrue(bcm.save(user, "test.1234/../../film")); - File bp = new File(blueprintFolder, "test1234film.blu"); + assertTrue(bcm.save(user, Util.sanitizeInput("test.1234/../../film"), "")); + File bp = new File(blueprintFolder, "test.1234_.._.._film.blu"); assertTrue(bp.exists()); verify(user).sendMessage("general.success"); } /** - * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}. * @throws IOException */ @Test @@ -407,14 +409,14 @@ public class BlueprintClipboardManagerTest { BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); bcm.load(BLUEPRINT); User user = mock(User.class); - assertTrue(bcm.save(user, "日本語の言葉")); + assertTrue(bcm.save(user, "日本語の言葉", "")); File bp = new File(blueprintFolder, "日本語の言葉.blu"); assertTrue(bp.exists()); verify(user).sendMessage("general.success"); } /** - * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}. + * Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}. * @throws IOException */ @Test @@ -430,8 +432,9 @@ public class BlueprintClipboardManagerTest { BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder); bcm.load(BLUEPRINT); User user = mock(User.class); - assertTrue(bcm.save(user, "日本語の言葉/../../../config")); - File bp = new File(blueprintFolder, "日本語の言葉config.blu"); + + assertTrue(bcm.save(user, Util.sanitizeInput("日本語の言葉/../../../config"), "")); + File bp = new File(blueprintFolder, "日本語の言葉_.._.._.._config.blu"); assertTrue(bp.exists()); verify(user).sendMessage("general.success"); } diff --git a/src/test/java/world/bentobox/bentobox/managers/BlueprintsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/BlueprintsManagerTest.java index 12c79afed..63bb1ba26 100644 --- a/src/test/java/world/bentobox/bentobox/managers/BlueprintsManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/BlueprintsManagerTest.java @@ -526,8 +526,6 @@ public class BlueprintsManagerTest { BlueprintsManager bpm = new BlueprintsManager(plugin); bpm.addBlueprintBundle(addon, bb); assertEquals("bundle", bpm.validate(addon, "bundle")); - // Mixed case - assertEquals("buNdle", bpm.validate(addon, "buNdle")); // Not there assertNull(bpm.validate(addon, "buNdle2")); } @@ -651,18 +649,19 @@ public class BlueprintsManagerTest { } /** - * Test method for {@link world.bentobox.bentobox.managers.BlueprintsManager#renameBlueprint(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.blueprints.Blueprint, java.lang.String)}. + * Test method for {@link world.bentobox.bentobox.managers.BlueprintsManager#renameBlueprint(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.blueprints.Blueprint, java.lang.String, java.lang.String)}. */ @Test public void testRenameBlueprint() { // Save it BlueprintsManager bpm = new BlueprintsManager(plugin); bpm.saveBlueprint(addon, defaultBp); + bpm.addBlueprint(addon, defaultBp); File blueprints = new File(dataFolder, BlueprintsManager.FOLDER_NAME); File d = new File(blueprints, "bedrock.blu"); assertTrue(d.exists()); // Rename it - bpm.renameBlueprint(addon, defaultBp, "bedrock2"); + bpm.renameBlueprint(addon, defaultBp, "bedrock2", ""); assertFalse(d.exists()); d = new File(blueprints, "bedrock2.blu"); assertTrue(d.exists()); From 173808b78701477e12f0b12186a940b583f56c26 Mon Sep 17 00:00:00 2001 From: BONNe Date: Thu, 29 Sep 2022 18:58:53 +0300 Subject: [PATCH 63/74] Adds glow berries protection. (#2027) Uuups... someone forgot to add them. Fixes #2020 --- .../listeners/flags/protection/BlockInteractionListener.java | 2 +- .../flags/protection/BlockInteractionListenerTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java index 5f87ea232..a98a17371 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java @@ -190,7 +190,7 @@ public class BlockInteractionListener extends FlagListener case DRAGON_EGG -> this.checkIsland(e, player, loc, Flags.DRAGON_EGG); case END_PORTAL_FRAME, RESPAWN_ANCHOR -> this.checkIsland(e, player, loc, Flags.PLACE_BLOCKS); case GLOW_ITEM_FRAME, ITEM_FRAME -> this.checkIsland(e, player, loc, Flags.ITEM_FRAME); - case SWEET_BERRY_BUSH -> this.checkIsland(e, player, loc, Flags.BREAK_BLOCKS); + case SWEET_BERRY_BUSH, CAVE_VINES -> this.checkIsland(e, player, loc, Flags.BREAK_BLOCKS); case CAKE -> this.checkIsland(e, player, loc, Flags.CAKE); case LAVA_CAULDRON -> { diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java index 0fe2ea98c..bdb0c5f56 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java @@ -113,6 +113,7 @@ public class BlockInteractionListenerTest extends AbstractCommonSetup { clickedBlocks.put(Material.ITEM_FRAME, Flags.ITEM_FRAME); clickedBlocks.put(Material.GLOW_ITEM_FRAME, Flags.ITEM_FRAME); clickedBlocks.put(Material.SWEET_BERRY_BUSH, Flags.BREAK_BLOCKS); + clickedBlocks.put(Material.CAVE_VINES, Flags.BREAK_BLOCKS); clickedBlocks.put(Material.CAKE, Flags.CAKE); clickedBlocks.put(Material.BEEHIVE, Flags.HIVE); clickedBlocks.put(Material.BEE_NEST, Flags.HIVE); From 755aeb866e9d574259e5027c50ce9a073c3928cd Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 30 Sep 2022 01:26:31 +0300 Subject: [PATCH 64/74] Rework player teleportation. I reworked the classes and some teleportation operations. Now teleportation should find the closest available spot, instead of always being the highest block at original Y location. Part of #1994. Also, I fixed an issue that portals stopped working if some conflicting options were enabled. Now portals will not work only if nether is disabled in config. --- .../world/bentobox/bentobox/BentoBox.java | 7 +- .../world/bentobox/bentobox/Settings.java | 50 + .../teleports/AbstractTeleportListener.java | 296 ++++++ .../teleports/PlayerTeleportListener.java | 438 +++++++++ .../teleport/ClosestSafeSpotTeleport.java | 887 ++++++++++++++++++ 5 files changed, 1674 insertions(+), 4 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java create mode 100644 src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java create mode 100644 src/main/java/world/bentobox/bentobox/util/teleport/ClosestSafeSpotTeleport.java diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java index 25bb0a268..dd004a790 100644 --- a/src/main/java/world/bentobox/bentobox/BentoBox.java +++ b/src/main/java/world/bentobox/bentobox/BentoBox.java @@ -19,7 +19,6 @@ import world.bentobox.bentobox.api.user.Notifier; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.commands.BentoBoxCommand; import world.bentobox.bentobox.database.DatabaseSetup; -import world.bentobox.bentobox.hooks.DynmapHook; import world.bentobox.bentobox.hooks.MultiverseCoreHook; import world.bentobox.bentobox.hooks.VaultHook; import world.bentobox.bentobox.hooks.placeholders.PlaceholderAPIHook; @@ -28,7 +27,7 @@ import world.bentobox.bentobox.listeners.BlockEndDragon; import world.bentobox.bentobox.listeners.DeathListener; import world.bentobox.bentobox.listeners.JoinLeaveListener; import world.bentobox.bentobox.listeners.PanelListenerManager; -import world.bentobox.bentobox.listeners.PortalTeleportationListener; +import world.bentobox.bentobox.listeners.teleports.PlayerTeleportListener; import world.bentobox.bentobox.listeners.StandardSpawnProtectionListener; import world.bentobox.bentobox.managers.AddonsManager; import world.bentobox.bentobox.managers.BlueprintsManager; @@ -288,8 +287,8 @@ public class BentoBox extends JavaPlugin { manager.registerEvents(new PanelListenerManager(), this); // Standard Nether/End spawns protection manager.registerEvents(new StandardSpawnProtectionListener(this), this); - // Nether portals - manager.registerEvents(new PortalTeleportationListener(this), this); + // Player portals + manager.registerEvents(new PlayerTeleportListener(this), this); // End dragon blocking manager.registerEvents(new BlockEndDragon(this), this); // Banned visitor commands diff --git a/src/main/java/world/bentobox/bentobox/Settings.java b/src/main/java/world/bentobox/bentobox/Settings.java index 28353ddd5..f91102324 100644 --- a/src/main/java/world/bentobox/bentobox/Settings.java +++ b/src/main/java/world/bentobox/bentobox/Settings.java @@ -314,6 +314,10 @@ public class Settings implements ConfigObject { @ConfigEntry(path = "island.safe-spot-search-vertical-range", since = "1.19.1") private int safeSpotSearchVerticalRange = 400; + @ConfigComment("By default, If the destination is not safe, the plugin will try to search for a safe spot around the destination,") + @ConfigEntry(path = "island.safe-spot-search-range", since = "1.21.0") + private int safeSpotSearchRange = 16; + /* WEB */ @ConfigComment("Toggle whether BentoBox can connect to GitHub to get data about updates and addons.") @ConfigComment("Disabling this will result in the deactivation of the update checker and of some other") @@ -907,19 +911,65 @@ public class Settings implements ConfigObject { this.minPortalSearchRadius = minPortalSearchRadius; } + + /** + * Gets safe spot search vertical range. + * + * @return the safe spot search vertical range + */ public int getSafeSpotSearchVerticalRange() { return safeSpotSearchVerticalRange; } + + /** + * Sets safe spot search vertical range. + * + * @param safeSpotSearchVerticalRange the safe spot search vertical range + */ public void setSafeSpotSearchVerticalRange(int safeSpotSearchVerticalRange) { this.safeSpotSearchVerticalRange = safeSpotSearchVerticalRange; } + + /** + * Is slow deletion boolean. + * + * @return the boolean + */ public boolean isSlowDeletion() { return slowDeletion; } + + /** + * Sets slow deletion. + * + * @param slowDeletion the slow deletion + */ public void setSlowDeletion(boolean slowDeletion) { this.slowDeletion = slowDeletion; } + + + /** + * Gets safe spot search range. + * + * @return the safe spot search range + */ + public int getSafeSpotSearchRange() + { + return safeSpotSearchRange; + } + + + /** + * Sets safe spot search range. + * + * @param safeSpotSearchRange the safe spot search range + */ + public void setSafeSpotSearchRange(int safeSpotSearchRange) + { + this.safeSpotSearchRange = safeSpotSearchRange; + } } diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java new file mode 100644 index 000000000..e2985d6e1 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java @@ -0,0 +1,296 @@ +// +// Created by BONNe +// Copyright - 2022 +// + + +package world.bentobox.bentobox.listeners.teleports; + + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.event.player.PlayerPortalEvent; +import org.eclipse.jdt.annotation.NonNull; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.database.objects.Island; + + +/** + * This abstract class contains all common methods for entity and player teleportation. + */ +public abstract class AbstractTeleportListener +{ + /** + * Instance of Teleportation processor. + * @param bentoBox BentoBox plugin. + */ + AbstractTeleportListener(@NonNull BentoBox bentoBox) + { + this.plugin = bentoBox; + this.inPortal = new HashSet<>(); + this.inTeleport = new HashSet<>(); + } + + +// --------------------------------------------------------------------- +// Section: Methods +// --------------------------------------------------------------------- + + + /** + * Get island at the given location + * @return optional island at given location + */ + protected Optional getIsland(Location location) + { + return this.plugin.getIslands().getProtectedIslandAt(location); + } + + + /** + * Check if vanilla portals should be used + * + * @param world - game mode world + * @param environment - environment + * @return true or false + */ + protected boolean isMakePortals(World world, World.Environment environment) + { + return this.plugin.getIWM().getAddon(world). + map(gameMode -> this.isMakePortals(gameMode, environment)). + orElse(false); + } + + + /** + * Check if vanilla portals should be used + * + * @param gameMode - game mode + * @param environment - environment + * @return true or false + */ + protected boolean isMakePortals(GameModeAddon gameMode, World.Environment environment) + { + return environment.equals(World.Environment.NETHER) ? + gameMode.getWorldSettings().isMakeNetherPortals() : + gameMode.getWorldSettings().isMakeEndPortals(); + } + + + /** + * Check if nether or end are generated + * + * @param overWorld - game world + * @param env - environment + * @return true or false + */ + protected boolean isAllowedInConfig(World overWorld, World.Environment env) + { + return env.equals(World.Environment.NETHER) ? + this.plugin.getIWM().isNetherGenerate(overWorld) : + this.plugin.getIWM().isEndGenerate(overWorld); + } + + + /** + * Check if the default nether or end are allowed by the server settings + * + * @param environment - environment + * @return true or false + */ + protected boolean isAllowedOnServer(World.Environment environment) + { + return environment.equals(World.Environment.NETHER) ? Bukkit.getAllowNether() : Bukkit.getAllowEnd(); + } + + + /** + * Check if nether or end islands are generated + * + * @param overWorld - over world + * @param environment - environment + * @return true or false + */ + protected boolean isIslandWorld(World overWorld, World.Environment environment) + { + return environment.equals(World.Environment.NETHER) ? + this.plugin.getIWM().isNetherIslands(overWorld) : + this.plugin.getIWM().isEndIslands(overWorld); + } + + + /** + * Get the nether or end world + * + * @param overWorld - over world + * @param environment - environment + * @return nether or end world + */ + protected World getNetherEndWorld(World overWorld, World.Environment environment) + { + return environment.equals(World.Environment.NETHER) ? + this.plugin.getIWM().getNetherWorld(overWorld) : + this.plugin.getIWM().getEndWorld(overWorld); + } + + + /** + * Check if the island has a nether or end island already + * + * @param island - island + * @param environment - environment + * @return true or false + */ + protected boolean hasPartnerIsland(Island island, World.Environment environment) + { + return environment.equals(World.Environment.NETHER) ? island.hasNetherIsland() : island.hasEndIsland(); + } + + + /** + * This method calculates the maximal search area for portal. + * @param location Location from which search should happen. + * @param island Island that contains the search point. + * @return Search range for portal. + */ + protected int calculateSearchRadius(Location location, Island island) + { + int diff; + + if (island.onIsland(location)) + { + // Find max x or max z + int x = Math.abs(island.getProtectionCenter().getBlockX() - location.getBlockX()); + int z = Math.abs(island.getProtectionCenter().getBlockZ() - location.getBlockZ()); + + diff = Math.min(this.plugin.getSettings().getSafeSpotSearchRange(), + island.getProtectionRange() - Math.max(x, z)); + } + else + { + diff = this.plugin.getSettings().getSafeSpotSearchRange(); + } + + return diff; + } + + + /** + * This method calculates location for portal. + * @param fromLocation Location from which teleportation happens. + * @param fromWorld World from which teleportation happens. + * @param toWorld The target world. + * @param environment Portal variant. + * @param canCreatePortal Indicates if portal should be created or not. + * @return Location for new portal. + */ + protected Location calculateLocation(Location fromLocation, + World fromWorld, + World toWorld, + World.Environment environment, + boolean canCreatePortal) + { + // Null check - not that useful + if (fromWorld == null || toWorld == null) + { + return null; + } + + Location toLocation = fromLocation.toVector().toLocation(toWorld); + + if (!this.isMakePortals(fromWorld, environment)) + { + toLocation = this.getIsland(fromLocation). + map(island -> island.getSpawnPoint(toWorld.getEnvironment())). + orElse(toLocation); + } + + // Limit Y to the min/max world height. + toLocation.setY(Math.max(Math.min(toLocation.getY(), toWorld.getMaxHeight()), toWorld.getMinHeight())); + + if (!canCreatePortal) + { + // Legacy portaling + return toLocation; + } + + // Make portals + // For anywhere other than the end - it is the player's location that is used + if (!environment.equals(World.Environment.THE_END)) + { + return toLocation; + } + + // If the-end then we want the platform to always be generated in the same place no matter where + // they enter the portal + final int x = fromLocation.getBlockX(); + final int z = fromLocation.getBlockZ(); + final int y = fromLocation.getBlockY(); + int i = x; + int j = z; + int k = y; + + // If the from is not a portal, then we have to find it + if (!fromLocation.getBlock().getType().equals(Material.END_PORTAL)) + { + // Find the portal - due to speed, it is possible that the player will be below or above the portal + for (k = toWorld.getMinHeight(); (k < fromWorld.getMaxHeight()) && + !fromWorld.getBlockAt(x, k, z).getType().equals(Material.END_PORTAL); k++); + } + + // Find the maximum x and z corner + for (; (i < x + 5) && fromWorld.getBlockAt(i, k, z).getType().equals(Material.END_PORTAL); i++) ; + for (; (j < z + 5) && fromWorld.getBlockAt(x, k, j).getType().equals(Material.END_PORTAL); j++) ; + + // Mojang end platform generation is: + // AIR + // AIR + // OBSIDIAN + // and player is placed on second air block above obsidian. + // If Y coordinate is below 2, then obsidian platform is not generated and player falls in void. + return new Location(toWorld, i, Math.max(toWorld.getMinHeight() + 2, k), j); + } + + + /** + * This method returns if missing islands should be generated uppon teleportation. + * Can happen only in non-custom generators. + * @param overWorld OverWorld + * @return {@code true} if missing islands must be pasted, {@code false} otherwise. + */ + protected boolean isPastingMissingIslands(World overWorld) + { + return this.plugin.getIWM().isPasteMissingIslands(overWorld) && + !this.plugin.getIWM().isUseOwnGenerator(overWorld); + } + + +// --------------------------------------------------------------------- +// Section: Variables +// --------------------------------------------------------------------- + + + /** + * BentoBox plugin instance. + */ + @NonNull + protected final BentoBox plugin; + + /** + * Set of entities that currently is inside portal. + */ + protected final Set inPortal; + + /** + * Set of entities that currently is in teleportation. + */ + protected final Set inTeleport; +} diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java new file mode 100644 index 000000000..76d33a6e3 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java @@ -0,0 +1,438 @@ +// +// Created by BONNe +// Copyright - 2022 +// + + +package world.bentobox.bentobox.listeners.teleports; + + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityPortalEnterEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerPortalEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.NonNull; + +import java.util.Objects; +import java.util.UUID; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.blueprints.Blueprint; +import world.bentobox.bentobox.blueprints.BlueprintPaster; +import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBundle; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; +import world.bentobox.bentobox.util.teleport.ClosestSafeSpotTeleport; + + +public class PlayerTeleportListener extends AbstractTeleportListener implements Listener +{ + /** + * Instantiates a new Portal teleportation listener. + * + * @param plugin the plugin + */ + public PlayerTeleportListener(@NonNull BentoBox plugin) + { + super(plugin); + } + + +// --------------------------------------------------------------------- +// Section: Listeners +// --------------------------------------------------------------------- + + + /** + * This listener checks player portal events and triggers appropriate methods to transfer + * players to the correct location in other dimension. + * + * This event is triggered when player is about to being teleported because of contact with the + * nether portal or end gateway portal (exit portal triggers respawn). + * + * This event is not called if nether/end is disabled in server settings. + * + * @param event the player portal event. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPlayerPortalEvent(PlayerPortalEvent event) + { + switch (event.getCause()) + { + case NETHER_PORTAL -> this.portalProcess(event, World.Environment.NETHER); + case END_PORTAL, END_GATEWAY -> this.portalProcess(event, World.Environment.THE_END); + } + } + + + /** + * Fires the event if nether or end is disabled at the system level + * + * @param event - EntityPortalEnterEvent + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPlayerPortal(EntityPortalEnterEvent event) + { + if (!EntityType.PLAYER.equals(event.getEntity().getType())) + { + // This handles only players. + return; + } + + Entity entity = event.getEntity(); + Material type = event.getLocation().getBlock().getType(); + UUID uuid = entity.getUniqueId(); + + if (this.inPortal.contains(uuid) || + !this.plugin.getIWM().inWorld(Util.getWorld(event.getLocation().getWorld()))) + { + return; + } + + this.inPortal.add(uuid); + + if (!Bukkit.getAllowNether() && type.equals(Material.NETHER_PORTAL)) + { + // Schedule a time + Bukkit.getScheduler().runTaskLater(this.plugin, () -> + { + // Check again if still in portal + if (this.inPortal.contains(uuid)) + { + // Create new PlayerPortalEvent + PlayerPortalEvent en = new PlayerPortalEvent((Player) entity, + event.getLocation(), + null, + PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, + 0, + false, + 0); + + this.onPlayerPortalEvent(en); + } + }, 40); + return; + } + + // End portals are instant transfer + if (!Bukkit.getAllowEnd() && (type.equals(Material.END_PORTAL) || type.equals(Material.END_GATEWAY))) + { + // Create new PlayerPortalEvent + PlayerPortalEvent en = new PlayerPortalEvent((Player) entity, + event.getLocation(), + null, + type.equals(Material.END_PORTAL) ? PlayerTeleportEvent.TeleportCause.END_PORTAL : PlayerTeleportEvent.TeleportCause.END_GATEWAY, + 0, + false, + 0); + + this.onPlayerPortalEvent(en); + } + } + + + /** + * Remove inPortal flag only when player exits the portal + * + * @param event player move event + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onExitPortal(PlayerMoveEvent event) + { + if (!this.inPortal.contains(event.getPlayer().getUniqueId())) + { + return; + } + + if (event.getTo() != null && !event.getTo().getBlock().getType().equals(Material.NETHER_PORTAL)) + { + // Player exits nether portal. + this.inPortal.remove(event.getPlayer().getUniqueId()); + this.inTeleport.remove(event.getPlayer().getUniqueId()); + } + } + + +// --------------------------------------------------------------------- +// Section: Processors +// --------------------------------------------------------------------- + + + private void portalProcess(PlayerPortalEvent event, World.Environment environment) + { + World fromWorld = event.getFrom().getWorld(); + World overWorld = Util.getWorld(fromWorld); + + if (overWorld == null || !this.plugin.getIWM().inWorld(overWorld)) + { + // Not teleporting from/to bentobox worlds. + return; + } + + if (!this.isAllowedInConfig(overWorld, environment)) + { + // World is disabled in config. Do not teleport player. + event.setCancelled(true); + return; + } + + if (!this.isAllowedOnServer(environment)) + { + // World is disabled in bukkit. Event is not triggered, but cancel by chance. + event.setCancelled(true); + } + + if (this.inTeleport.contains(event.getPlayer().getUniqueId())) + { + // Player is already in teleportation. + return; + } + + this.inTeleport.add(event.getPlayer().getUniqueId()); + + if (fromWorld.equals(overWorld) && !this.isIslandWorld(overWorld, environment)) + { + // This is not island world. Use standard nether or end world teleportation. + this.handleToStandardNetherOrEnd(event, overWorld, environment); + return; + } + + if (!fromWorld.equals(overWorld) && !this.isIslandWorld(overWorld, environment)) + { + // If entering a portal in the other world, teleport to a portal in overworld if + // there is one + this.handleFromStandardNetherOrEnd(event, overWorld, environment); + return; + } + + // To the nether/end or overworld. + World toWorld = !fromWorld.getEnvironment().equals(environment) ? + this.getNetherEndWorld(overWorld, environment) : overWorld; + + // Set whether portals should be created or not + event.setCanCreatePortal(this.isMakePortals(overWorld, environment)); + // Default 16 is will always end up placing portal as close to X/8 coordinate as possible. + // In most situations, 2 block value should be enough... I hope. + event.setCreationRadius(2); + + // Set the destination location + // If portals cannot be created, then destination is the spawn point, otherwise it's the vector + event.setTo(this.calculateLocation(event.getFrom(), fromWorld, toWorld, environment, event.getCanCreatePortal())); + + // Find the distance from edge of island's protection and set the search radius + this.getIsland(event.getTo()).ifPresent(island -> + event.setSearchRadius(this.calculateSearchRadius(event.getTo(), island))); + + // Check if there is an island there or not + if (this.isPastingMissingIslands(overWorld) && + this.isAllowedInConfig(overWorld, environment) && + this.isIslandWorld(overWorld, environment) && + this.getNetherEndWorld(overWorld, environment) != null && + this.getIsland(event.getTo()). + filter(island -> this.hasPartnerIsland(island, environment)). + map(island -> { + event.setCancelled(true); + this.pasteNewIsland(event.getPlayer(), event.getTo(), island, environment); + return true; + }). + orElse(false)) + { + // If there is no island, then processor already created island. Nothing to do more. + return; + } + + if (!event.isCancelled() && event.getCanCreatePortal()) + { + // Let the server teleport + return; + } + + if (environment.equals(World.Environment.THE_END)) + { + // Prevent death from hitting the ground while calculating location. + event.getPlayer().setVelocity(new Vector(0,0,0)); + event.getPlayer().setFallDistance(0); + } + + // If we do not generate portals, teleportation should happen manually with safe spot builder. + // Otherwise, we could end up with situations when player is placed in mid air, if teleportation + // is done instantly. + // Our safe spot task is triggered in next tick, however, end teleportation happens in the same tick. + // It is placed outside THE_END check, as technically it could happen with the nether portal too. + + // If there is a portal to go to already, then the player will go there + Bukkit.getScheduler().runTask(this.plugin, () -> { + if (!event.getPlayer().getWorld().equals(toWorld)) + { + // Else manually teleport entity + ClosestSafeSpotTeleport.builder(this.plugin). + entity(event.getPlayer()). + location(event.getTo()). + portal(). + successRunnable(() -> { + // Reset velocity just in case. + event.getPlayer().setVelocity(new Vector(0,0,0)); + event.getPlayer().setFallDistance(0); + }). + build(); + } + }); + } + + + /** + * Handle teleport from or to standard nether or end + * @param event - PlayerPortalEvent + * @param overWorld - over world + * @param environment - environment involved + */ + private void handleToStandardNetherOrEnd(PlayerPortalEvent event, + World overWorld, + World.Environment environment) + { + World toWorld = Objects.requireNonNull(this.getNetherEndWorld(overWorld, environment)); + Location spawnPoint = toWorld.getSpawnLocation(); + + // If going to the nether and nether portals are active then just teleport to approx location + if (environment.equals(World.Environment.NETHER) && + this.plugin.getIWM().getWorldSettings(overWorld).isMakeNetherPortals()) + { + spawnPoint = event.getFrom().toVector().toLocation(toWorld); + } + + // If spawn is set as 0,63,0 in the End then move it to 100, 50 ,0. + if (environment.equals(World.Environment.THE_END) && spawnPoint.getBlockX() == 0 && spawnPoint.getBlockZ() == 0) + { + // Set to the default end spawn + spawnPoint = new Location(toWorld, 100, 50, 0); + toWorld.setSpawnLocation(100, 50, 0); + } + + if (this.isAllowedOnServer(environment)) + { + // To Standard Nether or end + event.setTo(spawnPoint); + } + else + { + // Teleport to standard nether or end + ClosestSafeSpotTeleport.builder(this.plugin). + entity(event.getPlayer()). + location(spawnPoint). + portal(). + build(); + } + } + + + /** + * Handle teleport from or to standard nether or end (end is not possible because EXIT PORTAL triggers RESPAWN event) + * @param event - PlayerPortalEvent + * @param overWorld - over world + * @param environment - environment involved + */ + private void handleFromStandardNetherOrEnd(PlayerPortalEvent event, World overWorld, World.Environment environment) + { + if (environment.equals(World.Environment.NETHER) && + this.plugin.getIWM().getWorldSettings(overWorld).isMakeNetherPortals()) + { + // Set to location directly to the from location. + event.setTo(event.getFrom().toVector().toLocation(overWorld)); + + // Update portal search radius. + this.getIsland(event.getTo()).ifPresent(island -> + event.setSearchRadius(this.calculateSearchRadius(event.getTo(), island))); + + event.setCanCreatePortal(true); + // event.setCreationRadius(16); 16 is default creation radius. + } + else + { + // Cannot be portal. Should recalculate position. + + Location toLocation; + Island island = this.plugin.getIslandsManager().getIsland(overWorld, event.getPlayer().getUniqueId()); + + if (island == null) + { + // What to do? Player do not have an island! Check for spawn? + // TODO: SPAWN CHECK. + toLocation = event.getFrom(); + } + else + { + // TODO: Island Respawn, Bed, Default home location check. + toLocation = island.getSpawnPoint(World.Environment.NORMAL); + } + + event.setTo(toLocation); + } + + if (!this.isAllowedOnServer(environment)) + { + // Custom portal handling. + event.setCancelled(true); + + // Teleport to standard nether or end + ClosestSafeSpotTeleport.builder(this.plugin). + entity(event.getPlayer()). + location(event.getTo()). + portal(). + build(); + } + } + + + /** + * Pastes the default nether or end island and teleports the player to the island's spawn point + * @param player - player to teleport after pasting + * @param to - the fallback location if a spawn point is not part of the blueprint + * @param island - the island + * @param environment - NETHER or THE_END + */ + private void pasteNewIsland(Player player, + Location to, + Island island, + World.Environment environment) + { + // Paste then teleport player + this.plugin.getIWM().getAddon(island.getWorld()).ifPresent(addon -> + { + // Get the default bundle's nether or end blueprint + BlueprintBundle blueprintBundle = plugin.getBlueprintsManager().getDefaultBlueprintBundle(addon); + + if (blueprintBundle != null) + { + Blueprint bluePrint = this.plugin.getBlueprintsManager().getBlueprints(addon). + get(blueprintBundle.getBlueprint(environment)); + + if (bluePrint != null) + { + new BlueprintPaster(this.plugin, bluePrint, to.getWorld(), island). + paste(). + thenAccept(state -> ClosestSafeSpotTeleport.builder(this.plugin). + entity(player). + location(island.getSpawnPoint(environment) == null ? to : island.getSpawnPoint(environment)). + portal(). + build()); + } + else + { + this.plugin.logError("Could not paste default island in nether or end. " + + "Is there a nether-island or end-island blueprint?"); + } + } + }); + } +} diff --git a/src/main/java/world/bentobox/bentobox/util/teleport/ClosestSafeSpotTeleport.java b/src/main/java/world/bentobox/bentobox/util/teleport/ClosestSafeSpotTeleport.java new file mode 100644 index 000000000..c24ff8c89 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/util/teleport/ClosestSafeSpotTeleport.java @@ -0,0 +1,887 @@ +// +// Created by BONNe +// Copyright - 2022 +// + + +package world.bentobox.bentobox.util.teleport; + + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.Nullable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Pair; +import world.bentobox.bentobox.util.Util; + + +public class ClosestSafeSpotTeleport +{ + /** + * Teleports and entity to a safe spot on island + * + * @param builder - safe spot teleport builder + */ + ClosestSafeSpotTeleport(Builder builder) + { + this.plugin = builder.getPlugin(); + this.entity = builder.getEntity(); + this.location = builder.getLocation(); + this.portal = builder.isPortal(); + + this.successRunnable = builder.getSuccessRunnable(); + this.failRunnable = builder.getFailRunnable(); + + this.failureMessage = builder.getFailureMessage(); + + this.result = builder.getResult(); + this.world = Objects.requireNonNull(this.location.getWorld()); + + this.cancelIfFail = builder.isCancelIfFail(); + + // Try starting location + Util.getChunkAtAsync(this.location).thenRun(this::checkLocation); + } + + + /** + * This is main method that triggers safe spot search. + * It starts with the given location and afterwards checks all blocks in required area. + */ + private void checkLocation() + { + if (this.plugin.getIslandsManager().isSafeLocation(this.location)) + { + if (!this.portal) + { + // If this is not a portal teleport, then go to the safe location immediately + this.teleportEntity(this.location); + // Position search is completed. Quit faster. + return; + } + } + + // Players should not be teleported outside protection range if they already are in it. + this.boundingBox = this.plugin.getIslandsManager().getIslandAt(this.location). + map(Island::getProtectionBoundingBox). + orElseGet(() -> { + int protectionRange = this.plugin.getIWM().getIslandProtectionRange(this.world); + + return new BoundingBox(this.location.getBlockX() - protectionRange, + Math.max(this.world.getMinHeight(), this.location.getBlockY() - protectionRange), + this.location.getBlockZ() - protectionRange, + this.location.getBlockX() + protectionRange, + Math.min(this.world.getMaxHeight(), this.location.getBlockY() + protectionRange), + this.location.getBlockZ() + protectionRange); + }); + + // The maximal range of search. + this.range = Math.min(this.plugin.getSettings().getSafeSpotSearchRange(), (int) this.boundingBox.getWidthX() / 2); + + // The block queue contains all possible positions where player can be teleported. The queue will not be populated + // with all blocks, as the validation would not allow it.ss + this.blockQueue = new PriorityQueue<>(this.range * 2, ClosestSafeSpotTeleport.POSITION_COMPARATOR); + + // Get chunks to scan + this.chunksToScanIterator = this.getChunksToScan().iterator(); + + // Start a recurring task until done or cancelled + this.task = Bukkit.getScheduler().runTaskTimer(this.plugin, this::gatherChunks, 0L, CHUNK_LOAD_SPEED); + } + + + /** + * This method loads all chunks in async and populates blockQueue with all blocks. + */ + private void gatherChunks() + { + // Set a flag so this is only run if it's not already in progress + if (this.checking.get()) + { + return; + } + + this.checking.set(true); + + if (!this.portal && !this.blockQueue.isEmpty() && this.blockQueue.peek().distance() < 5) + { + // Position is found? Well most likely (not in all situations) position in block queue is already + // the best position. The only bad situations could happen if position is on chunk borders. + this.finishTask(); + return; + } + + if (!this.chunksToScanIterator.hasNext()) + { + // Chunk scanning has completed. Now check positions. + this.finishTask(); + return; + } + + // Get the chunk + Pair chunkPair = this.chunksToScanIterator.next(); + this.chunksToScanIterator.remove(); + + // Get the chunk snapshot and scan it + Util.getChunkAtAsync(this.world, chunkPair.x, chunkPair.z). + thenApply(Chunk::getChunkSnapshot). + whenCompleteAsync((snapshot, e) -> + { + if (snapshot != null) + { + // Find best spot based on collected information chunks. + this.scanAndPopulateBlockQueue(snapshot); + } + + this.checking.set(false); + }); + } + + + /** + * Gets a set of chunk coordinates that will be scanned. + * + * @return - list of chunk coordinates to be scanned + */ + private List> getChunksToScan() + { + List> chunksToScan = new ArrayList<>(); + + int x = this.location.getBlockX(); + int z = this.location.getBlockZ(); + + int range = 20; + + // Normalize block coordinates to chunk coordinates and add extra 1 for visiting. + int numberOfChunks = (((x + range) >> 4) - ((x - range) >> 4) + 1) * + (((z + range) >> 4) - ((z - range) >> 4) + 1); + + // Ideally it would be if visitor switch from clockwise to counter-clockwise if X % 16 < 8 and + // up to down if Z % 16 < 8. + + int offsetX = 0; + int offsetZ = 0; + + for (int i = 0; i < numberOfChunks; ++i) + { + int locationX = x + (offsetX << 4); + int locationZ = z + (offsetZ << 4); + + this.addChunk(chunksToScan, new Pair<>(locationX, locationZ), new Pair<>(locationX >> 4, locationZ >> 4)); + + if (Math.abs(offsetX) <= Math.abs(offsetZ) && (offsetX != offsetZ || offsetX >= 0)) + { + offsetX += ((offsetZ >= 0) ? 1 : -1); + } + else + { + offsetZ += ((offsetX >= 0) ? -1 : 1); + } + } + + return chunksToScan; + } + + + /** + * This method adds chunk coordinates to the given chunksToScan list. + * The limitation is that if location is in island, then block coordinate must also be in island space. + * @param chunksToScan List of chunks that will be scanned. + * @param blockCoord Block coordinates that must be in island. + * @param chunkCoord Chunk coordinate. + */ + private void addChunk(List> chunksToScan, + Pair blockCoord, + Pair chunkCoord) + { + if (!chunksToScan.contains(chunkCoord) && + this.plugin.getIslandsManager().getIslandAt(this.location). + map(is -> is.inIslandSpace(blockCoord)).orElse(true)) + { + chunksToScan.add(chunkCoord); + } + } + + + /** + * This method populates block queue with all blocks that player can be teleported to. + * Add only positions that are inside BoundingBox and is safe for teleportation. + * @param chunkSnapshot Spigot Chunk Snapshot with blocks. + */ + private void scanAndPopulateBlockQueue(ChunkSnapshot chunkSnapshot) + { + int startY = this.location.getBlockY(); + int minY = this.world.getMinHeight(); + int maxY = this.world.getMaxHeight(); + + Vector blockVector = new Vector(this.location.getBlockX(), this.location.getBlockY(), this.location.getBlockZ()); + + int chunkX = chunkSnapshot.getX() << 4; + int chunkZ = chunkSnapshot.getZ() << 4; + + for (int x = 0; x < 16; x++) + { + for (int z = 0; z < 16; z++) + { + for (int y = Math.max(minY, startY - this.range); y < Math.min(maxY, startY + this.range); y++) + { + Vector positionVector = new Vector(chunkX + x, y, chunkZ + z); + + if (this.boundingBox.contains(positionVector)) + { + // Process positions that are inside bounding box of search area. + + PositionData positionData = new PositionData( + positionVector, + chunkSnapshot.getBlockType(x, y - 1, z), + y < maxY ? chunkSnapshot.getBlockType(x, y, z) : null, + y + 1 < maxY ? chunkSnapshot.getBlockType(x, y + 1, z) : null, + blockVector.distanceSquared(positionVector)); + + if (this.plugin.getIslandsManager().checkIfSafe(this.world, + positionData.block, + positionData.spaceOne, + positionData.spaceTwo)) + { + // Add only safe locations to the queue. + this.blockQueue.add(positionData); + } + } + } + } + } + } + + + /** + * This method finishes the chunk loading task and checks from all remaining positions in block queue + * to find the best location for teleportation. + * + * This method stops position finding task and process teleporation. + */ + private void finishTask() + { + // Still Async! + // Nothing left to check and still not canceled + this.task.cancel(); + + if (this.scanBlockQueue()) + { + return; + } + + if (this.portal && this.noPortalPosition != null) + { + this.teleportEntity(this.noPortalPosition); + } + else if (this.entity instanceof Player player) + { + // Return to main thread and teleport the player + Bukkit.getScheduler().runTask(this.plugin, () -> + { + // Failed, no safe spot + if (!this.failureMessage.isEmpty()) + { + User.getInstance(this.entity).notify(this.failureMessage); + } + + // Check highest block + Block highestBlock = this.world.getHighestBlockAt(this.location); + + if (highestBlock.getType().isSolid() && + this.plugin.getIslandsManager().isSafeLocation(highestBlock.getLocation())) + { + // Try to teleport player to the highest block. + this.asyncTeleport(highestBlock.getLocation().add(new Vector(0.5D, 0D, 0.5D))); + return; + } + else if (!this.plugin.getIWM().inWorld(this.entity.getLocation())) + { + // Last resort + player.performCommand("spawn"); + } + else if (!this.cancelIfFail) + { + // Create a spot for the player to be + if (this.world.getEnvironment().equals(World.Environment.NETHER)) + { + this.makeAndTeleport(Material.NETHERRACK); + } + else if (this.world.getEnvironment().equals(World.Environment.THE_END)) + { + this.makeAndTeleport(Material.END_STONE); + } + else + { + this.makeAndTeleport(Material.COBBLESTONE); + } + } + + if (this.failRunnable != null) + { + Bukkit.getScheduler().runTask(this.plugin, this.failRunnable); + } + + this.result.complete(false); + }); + } + else + { + // We do not teleport entities if position failed. + + if (this.failRunnable != null) + { + Bukkit.getScheduler().runTask(this.plugin, this.failRunnable); + } + + this.result.complete(false); + } + } + + + /** + * This method creates a spot in start location for player to be teleported to. It creates 2 base material blocks + * above location and fills the space between them with air. + * @param baseMaterial Material that will be for top and bottom block. + */ + private void makeAndTeleport(Material baseMaterial) + { + this.location.getBlock().getRelative(BlockFace.DOWN).setType(baseMaterial, false); + this.location.getBlock().setType(Material.AIR, false); + this.location.getBlock().getRelative(BlockFace.UP).setType(Material.AIR, false); + this.location.getBlock().getRelative(BlockFace.UP).getRelative(BlockFace.UP).setType(baseMaterial, false); + + // Teleport player to the location of the empty space. + this.asyncTeleport(this.location.clone().add(new Vector(0.5D, 0D, 0.5D))); + } + + + /** + * This method scans all populated positions and returns true if position is found, or false, if not. + * @return {@code true} if safe position is found, otherwise false. + */ + private boolean scanBlockQueue() + { + boolean blockFound = false; + + while (!this.blockQueue.isEmpty() && !blockFound) + { + blockFound = this.checkPosition(this.blockQueue.poll()); + } + + return blockFound; + } + + + /** + * This method triggers a task that will teleport entity in a main thread. + */ + private void teleportEntity(final Location location) + { + // Return to main thread and teleport the player + Bukkit.getScheduler().runTask(this.plugin, () -> this.asyncTeleport(location)); + } + + + /** + * This method performs async teleportation and runs end tasks for spot-finder. + * @param location Location where player should be teleported. + */ + private void asyncTeleport(final Location location) + { + Util.teleportAsync(this.entity, location).thenRun(() -> + { + if (this.successRunnable != null) + { + Bukkit.getScheduler().runTask(this.plugin, this.successRunnable); + } + + this.result.complete(true); + }); + } + + + /** + * This method checks if given position is valid for teleportation. + * If query should find portal, then it marks first best position as noPortalPosition and continues + * to search for a valid portal. + * If query is not in portal mode, then return first valid position. + * @param positionData Position data that must be checked. + * @return {@code true} if position is found and no extra processing required, {@code false} otherwise. + */ + private boolean checkPosition(PositionData positionData) + { + if (this.portal) + { + if (Material.NETHER_PORTAL.equals(positionData.spaceOne()) || + Material.NETHER_PORTAL.equals(positionData.spaceTwo())) + { + // Portal is found. Teleport entity to the portal location. + this.teleportEntity(new Location(this.world, + positionData.vector().getBlockX() + 0.5, + positionData.vector().getBlockY() + 0.1, + positionData.vector().getBlockZ() + 0.5, + this.location.getYaw(), + this.location.getPitch())); + + // Position found and player can is already teleported to it. + return true; + } + else if (this.noPortalPosition == null) + { + // Mark first incoming position as the best for teleportation. + this.noPortalPosition = new Location(this.world, + positionData.vector().getBlockX() + 0.5, + positionData.vector().getBlockY() + 0.1, + positionData.vector().getBlockZ() + 0.5, + this.location.getYaw(), + this.location.getPitch()); + } + } + else + { + // First best position should be valid for teleportation. + this.teleportEntity(new Location(this.world, + positionData.vector().getBlockX() + 0.5, + positionData.vector().getBlockY() + 0.1, + positionData.vector().getBlockZ() + 0.5, + this.location.getYaw(), + this.location.getPitch())); + return true; + } + + return false; + } + + + /** + * PositionData record holds information about position where player will be teleported. + * @param vector Vector of the position. + * @param distance Distance till the position. + * @param block Block on which player will be placed. + * @param spaceOne One block above block. + * @param spaceTwo Two blocks above block. + */ + private record PositionData(Vector vector, Material block, Material spaceOne, Material spaceTwo, double distance) {} + + + public static Builder builder(BentoBox plugin) + { + return new Builder(plugin); + } + + +// --------------------------------------------------------------------- +// Section: Builder +// --------------------------------------------------------------------- + + + public static class Builder + { + private Builder(BentoBox plugin) + { + this.plugin = plugin; + this.result = new CompletableFuture<>(); + } + + // --------------------------------------------------------------------- + // Section: Builders + // --------------------------------------------------------------------- + + + + /** + * Set who or what is going to teleport + * + * @param entity entity to teleport + * @return Builder + */ + public Builder entity(Entity entity) + { + this.entity = entity; + return this; + } + + + /** + * Set the desired location + * + * @param location the location + * @return Builder + */ + public Builder location(Location location) + { + this.location = location; + return this; + } + + + /** + * This is a portal teleportation + * + * @return Builder + */ + public Builder portal() + { + this.portal = true; + return this; + } + + + /** + * This is a successRunnable for teleportation + * + * @return Builder + */ + public Builder successRunnable(Runnable successRunnable) + { + this.successRunnable = successRunnable; + return this; + } + + + /** + * Try to teleport the player + * + * @return ClosestSafeSpotTeleport + */ + @Nullable + public ClosestSafeSpotTeleport build() + { + // Error checking + if (this.entity == null) + { + this.plugin.logError("Attempt to safe teleport a null entity!"); + this.result.complete(null); + return null; + } + + if (this.location == null) + { + this.plugin.logError("Attempt to safe teleport to a null location!"); + this.result.complete(null); + return null; + } + + if (this.location.getWorld() == null) + { + this.plugin.logError("Attempt to safe teleport to a null world!"); + this.result.complete(null); + return null; + } + + if (this.failureMessage.isEmpty() && this.entity instanceof Player) + { + this.failureMessage = "general.errors.no-safe-location-found"; + } + + return new ClosestSafeSpotTeleport(this); + } + + + // --------------------------------------------------------------------- + // Section: Getters + // --------------------------------------------------------------------- + + + /** + * Gets plugin. + * + * @return the plugin + */ + public BentoBox getPlugin() + { + return this.plugin; + } + + + /** + * Gets result. + * + * @return the result + */ + public CompletableFuture getResult() + { + return this.result; + } + + + /** + * Gets entity. + * + * @return the entity + */ + public Entity getEntity() + { + return this.entity; + } + + + /** + * Gets location. + * + * @return the location + */ + public Location getLocation() + { + return this.location; + } + + + /** + * Gets world. + * + * @return the world + */ + public World getWorld() + { + return this.world; + } + + + /** + * Gets success runnable. + * + * @return the success runnable + */ + public Runnable getSuccessRunnable() + { + return this.successRunnable; + } + + + /** + * Gets fail runnable. + * + * @return the fail runnable + */ + public Runnable getFailRunnable() + { + return this.failRunnable; + } + + + /** + * Gets failure message. + * + * @return the failure message + */ + public String getFailureMessage() + { + return this.failureMessage; + } + + + /** + * Is portal boolean. + * + * @return the boolean + */ + public boolean isPortal() + { + return this.portal; + } + + + /** + * Is cancel if fail boolean. + * + * @return the boolean + */ + public boolean isCancelIfFail() + { + return this.cancelIfFail; + } + + + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- + + + /** + * BentoBox plugin instance. + */ + private final BentoBox plugin; + + /** + * CompletableFuture that is triggered upon finishing position searching. + */ + private final CompletableFuture result; + + /** + * Entity that will be teleported. + */ + private Entity entity; + + /** + * Start location of teleportation. + */ + private Location location; + + /** + * World where teleportation happens. + */ + private World world; + + /** + * Runnable that will be triggered after successful teleportation. + */ + private Runnable successRunnable; + + /** + * Runnable that will be triggered after failing teleportation. + */ + private Runnable failRunnable; + + /** + * Stores the failure message that is sent to a player. + */ + private String failureMessage = ""; + + /** + * Boolean that indicates if teleportation should search for portal. + */ + private boolean portal; + + /** + * Boolean that indicates if failing teleport should cancel it or create spot for player. + */ + private boolean cancelIfFail; + } + + +// --------------------------------------------------------------------- +// Section: Constants +// --------------------------------------------------------------------- + + /** + * This comparator sorts position data based in order: + * - the smallest distance value + * - the smallest x value + * - the smallest z value + * - the smallest y value + */ + private final static Comparator POSITION_COMPARATOR = Comparator.comparingDouble(PositionData::distance). + thenComparingInt(position -> position.vector().getBlockX()). + thenComparingInt(position -> position.vector().getBlockZ()). + thenComparingInt(position -> position.vector().getBlockY()); + + /** + * Stores chunk load speed. + */ + private static final long CHUNK_LOAD_SPEED = 1; + +// --------------------------------------------------------------------- +// Section: Variables +// --------------------------------------------------------------------- + + /** + * BentoBox plugin instance. + */ + private final BentoBox plugin; + + /** + * Entity that will be teleported. + */ + private final Entity entity; + + /** + * Start location of teleportation. + */ + private final Location location; + + /** + * World where teleportation happens. + */ + private final World world; + + /** + * Runnable that will be triggered after successful teleportation. + */ + private final Runnable successRunnable; + + /** + * Runnable that will be triggered after failing teleportation. + */ + private final Runnable failRunnable; + + /** + * Stores the failure message that is sent to a player. + */ + private final String failureMessage; + + /** + * CompletableFuture that is triggered upon finishing position searching. + */ + private final CompletableFuture result; + + /** + * Boolean that indicates if teleportation should search for portal. + */ + private final boolean portal; + + /** + * Boolean that indicates if failing teleport should cancel it or create spot for player. + */ + private final boolean cancelIfFail; + + /** + * Local variable that indicates if current process is running. + */ + private final AtomicBoolean checking = new AtomicBoolean(); + + /** + * The distance from starting location in all directions where new position will be searched. + */ + private int range; + + /** + * Block Queue for all blocks that should be validated. + */ + private Queue blockQueue; + + /** + * List of chunks that will be scanned for positions. + */ + private Iterator> chunksToScanIterator; + + /** + * BoundingBox where teleportation can happen. Areas outside are illegal. + */ + private BoundingBox boundingBox; + + /** + * This method returns first best available spot if portal was not found in search area. + */ + private Location noPortalPosition; + + /** + * Bukkit task that processes chunks. + */ + private BukkitTask task; +} + From eb5147e71024356e7bd3ed18b6af3845d9b2ae10 Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 30 Sep 2022 01:31:05 +0300 Subject: [PATCH 65/74] Change to direct PlayerTeleportListener#portalProcess method call. --- .../listeners/teleports/PlayerTeleportListener.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java index 76d33a6e3..f91eafb22 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java @@ -119,7 +119,7 @@ public class PlayerTeleportListener extends AbstractTeleportListener implements false, 0); - this.onPlayerPortalEvent(en); + this.portalProcess(en, World.Environment.NETHER); } }, 40); return; @@ -137,7 +137,7 @@ public class PlayerTeleportListener extends AbstractTeleportListener implements false, 0); - this.onPlayerPortalEvent(en); + this.portalProcess(en, World.Environment.THE_END); } } @@ -169,6 +169,11 @@ public class PlayerTeleportListener extends AbstractTeleportListener implements // --------------------------------------------------------------------- + /** + * This method process player teleportation to new dimension. + * @param event Event that triggers teleportation. + * @param environment Environment of portal type. + */ private void portalProcess(PlayerPortalEvent event, World.Environment environment) { World fromWorld = event.getFrom().getWorld(); From 97c4cf883f59cdf88ab23dff285fa03baa814ea9 Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 30 Sep 2022 02:25:44 +0300 Subject: [PATCH 66/74] Implement proper end exit portal handling. When players will exit the end-gate, they will be teleported to proper island, instead of world spawn point. The only way how players can be teleported to the spawn is if they do not have islands. --- .../teleports/AbstractTeleportListener.java | 43 +++++++++-- .../teleports/PlayerTeleportListener.java | 77 +++++++++++++++---- 2 files changed, 99 insertions(+), 21 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java index e2985d6e1..fa618b899 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java @@ -11,12 +11,10 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; -import org.bukkit.event.player.PlayerPortalEvent; +import org.bukkit.entity.Player; import org.eclipse.jdt.annotation.NonNull; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; +import org.eclipse.jdt.annotation.Nullable; +import java.util.*; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.GameModeAddon; @@ -37,6 +35,7 @@ public abstract class AbstractTeleportListener this.plugin = bentoBox; this.inPortal = new HashSet<>(); this.inTeleport = new HashSet<>(); + this.teleportOrigin = new HashMap<>(); } @@ -51,7 +50,17 @@ public abstract class AbstractTeleportListener */ protected Optional getIsland(Location location) { - return this.plugin.getIslands().getProtectedIslandAt(location); + return this.plugin.getIslandsManager().getProtectedIslandAt(location); + } + + + /** + * Get island for given player at the given world. + * @return optional island at given world. + */ + protected Optional getIsland(World world, Player player) + { + return Optional.ofNullable(this.plugin.getIslandsManager().getIsland(world, player.getUniqueId())); } @@ -260,6 +269,23 @@ public abstract class AbstractTeleportListener } + /** + * This method returns spawn location for given world. + * @param world World which spawn point must be returned. + * @return Spawn location for world or null. + */ + @Nullable + protected Location getSpawnLocation(World world) + { + return this.plugin.getIslandsManager().getSpawn(world).map(island -> + island.getSpawnPoint(World.Environment.NORMAL) == null ? + island.getCenter() : + island.getSpawnPoint(World.Environment.NORMAL)). + orElse(this.plugin.getIslands().isSafeLocation(world.getSpawnLocation()) ? + world.getSpawnLocation() : null); + } + + /** * This method returns if missing islands should be generated uppon teleportation. * Can happen only in non-custom generators. @@ -289,6 +315,11 @@ public abstract class AbstractTeleportListener */ protected final Set inPortal; + /** + * Map that links entities origin of teleportation. Used for respawning. + */ + protected final Map teleportOrigin; + /** * Set of entities that currently is in teleportation. */ diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java index f91eafb22..2759946b4 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java @@ -20,6 +20,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityPortalEnterEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerPortalEvent; +import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; @@ -101,6 +102,8 @@ public class PlayerTeleportListener extends AbstractTeleportListener implements } this.inPortal.add(uuid); + // Add original world for respawning. + this.teleportOrigin.put(uuid, event.getLocation().getWorld()); if (!Bukkit.getAllowNether() && type.equals(Material.NETHER_PORTAL)) { @@ -160,10 +163,65 @@ public class PlayerTeleportListener extends AbstractTeleportListener implements // Player exits nether portal. this.inPortal.remove(event.getPlayer().getUniqueId()); this.inTeleport.remove(event.getPlayer().getUniqueId()); + this.teleportOrigin.remove(event.getPlayer().getUniqueId()); } } + /** + * Player respawn event is triggered when player enters exit portal at the end. + * This will take over respawn mechanism and place player on island. + * @param event player respawn event + */ + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPlayerExitPortal(PlayerRespawnEvent event) + { + if (!this.teleportOrigin.containsKey(event.getPlayer().getUniqueId())) + { + // Player is already processed. + return; + } + + World fromWorld = this.teleportOrigin.get(event.getPlayer().getUniqueId()); + World overWorld = Util.getWorld(fromWorld); + + if (overWorld == null || !this.plugin.getIWM().inWorld(overWorld)) + { + // Not teleporting from/to bentobox worlds. + return; + } + + this.getIsland(overWorld, event.getPlayer()).ifPresentOrElse(island -> { + if (!island.onIsland(event.getRespawnLocation())) + { + // If respawn location is outside island protection range, change location to the + // spawn in overworld or home location. + Location location = island.getSpawnPoint(World.Environment.NORMAL); + + if (location == null) + { + // No spawn point. Rare thing. Well, use island protection center. + location = island.getProtectionCenter(); + } + + event.setRespawnLocation(location); + } + }, + () -> { + // Player does not an island. Try to get spawn island, and if that fails, use world spawn point. + // If spawn point is not safe, do nothing. Let server handle it. + + Location spawnLocation = this.getSpawnLocation(overWorld); + + if (spawnLocation != null) + { + event.setRespawnLocation(spawnLocation); + } + }); + } + + + // --------------------------------------------------------------------- // Section: Processors // --------------------------------------------------------------------- @@ -365,21 +423,10 @@ public class PlayerTeleportListener extends AbstractTeleportListener implements else { // Cannot be portal. Should recalculate position. - - Location toLocation; - Island island = this.plugin.getIslandsManager().getIsland(overWorld, event.getPlayer().getUniqueId()); - - if (island == null) - { - // What to do? Player do not have an island! Check for spawn? - // TODO: SPAWN CHECK. - toLocation = event.getFrom(); - } - else - { - // TODO: Island Respawn, Bed, Default home location check. - toLocation = island.getSpawnPoint(World.Environment.NORMAL); - } + // TODO: Currently, it is always spawn location. However, default home must be assigned. + Location toLocation = this.getIsland(overWorld, event.getPlayer()). + map(island -> island.getSpawnPoint(World.Environment.NORMAL)). + orElse(event.getFrom()); event.setTo(toLocation); } From aa7abe02bf812732db5c053ff08eb9e108de3850 Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 30 Sep 2022 02:33:07 +0300 Subject: [PATCH 67/74] Teleport island-less player to the spawn if he exits nether --- .../listeners/teleports/PlayerTeleportListener.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java index 2759946b4..50d4d5928 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java @@ -426,7 +426,13 @@ public class PlayerTeleportListener extends AbstractTeleportListener implements // TODO: Currently, it is always spawn location. However, default home must be assigned. Location toLocation = this.getIsland(overWorld, event.getPlayer()). map(island -> island.getSpawnPoint(World.Environment.NORMAL)). - orElse(event.getFrom()); + orElseGet(() -> { + // If player do not have island, try spawn. + Location spawnLocation = this.getSpawnLocation(overWorld); + return spawnLocation == null ? + event.getFrom().toVector().toLocation(overWorld) : + spawnLocation; + }); event.setTo(toLocation); } From d197ce8bea9583fb388f74a7bfbdd5bf8fc8fecd Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 30 Sep 2022 02:34:46 +0300 Subject: [PATCH 68/74] Add class description. --- .../bentobox/listeners/teleports/PlayerTeleportListener.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java index 50d4d5928..d036312d2 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java @@ -37,6 +37,11 @@ import world.bentobox.bentobox.util.Util; import world.bentobox.bentobox.util.teleport.ClosestSafeSpotTeleport; +/** + * This class handles player teleportation between dimensions. + * + * @author tastybento and BONNe + */ public class PlayerTeleportListener extends AbstractTeleportListener implements Listener { /** From 4458d16274a8aa28cb5a3973e5c7aa78a19884f8 Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 30 Sep 2022 14:34:12 +0300 Subject: [PATCH 69/74] Fixes incorrect condition for missing island check. --- .../bentobox/listeners/teleports/PlayerTeleportListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java index d036312d2..d6b862c21 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java @@ -308,7 +308,7 @@ public class PlayerTeleportListener extends AbstractTeleportListener implements this.isIslandWorld(overWorld, environment) && this.getNetherEndWorld(overWorld, environment) != null && this.getIsland(event.getTo()). - filter(island -> this.hasPartnerIsland(island, environment)). + filter(island -> !this.hasPartnerIsland(island, environment)). map(island -> { event.setCancelled(true); this.pasteNewIsland(event.getPlayer(), event.getTo(), island, environment); From 682d835961cb13e23f6f2e05db93951fe3a3c725 Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 30 Sep 2022 14:34:28 +0300 Subject: [PATCH 70/74] Add all dimension checks for methods. --- .../teleports/AbstractTeleportListener.java | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java index fa618b899..bb8638cc9 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/AbstractTeleportListener.java @@ -19,6 +19,7 @@ import java.util.*; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; /** @@ -88,9 +89,11 @@ public abstract class AbstractTeleportListener */ protected boolean isMakePortals(GameModeAddon gameMode, World.Environment environment) { - return environment.equals(World.Environment.NETHER) ? - gameMode.getWorldSettings().isMakeNetherPortals() : - gameMode.getWorldSettings().isMakeEndPortals(); + return switch (environment) { + case NETHER -> gameMode.getWorldSettings().isMakeNetherPortals(); + case THE_END -> gameMode.getWorldSettings().isMakeEndPortals(); + default -> false; + }; } @@ -98,14 +101,16 @@ public abstract class AbstractTeleportListener * Check if nether or end are generated * * @param overWorld - game world - * @param env - environment + * @param environment - environment * @return true or false */ - protected boolean isAllowedInConfig(World overWorld, World.Environment env) + protected boolean isAllowedInConfig(World overWorld, World.Environment environment) { - return env.equals(World.Environment.NETHER) ? - this.plugin.getIWM().isNetherGenerate(overWorld) : - this.plugin.getIWM().isEndGenerate(overWorld); + return switch (environment) { + case NETHER -> this.plugin.getIWM().isNetherGenerate(overWorld); + case THE_END -> this.plugin.getIWM().isEndGenerate(overWorld); + default -> true; + }; } @@ -117,7 +122,11 @@ public abstract class AbstractTeleportListener */ protected boolean isAllowedOnServer(World.Environment environment) { - return environment.equals(World.Environment.NETHER) ? Bukkit.getAllowNether() : Bukkit.getAllowEnd(); + return switch (environment) { + case NETHER -> Bukkit.getAllowNether(); + case THE_END -> Bukkit.getAllowEnd(); + default -> true; + }; } @@ -130,9 +139,11 @@ public abstract class AbstractTeleportListener */ protected boolean isIslandWorld(World overWorld, World.Environment environment) { - return environment.equals(World.Environment.NETHER) ? - this.plugin.getIWM().isNetherIslands(overWorld) : - this.plugin.getIWM().isEndIslands(overWorld); + return switch (environment) { + case NETHER -> this.plugin.getIWM().isNetherIslands(overWorld); + case THE_END -> this.plugin.getIWM().isEndIslands(overWorld); + default -> true; + }; } @@ -145,9 +156,11 @@ public abstract class AbstractTeleportListener */ protected World getNetherEndWorld(World overWorld, World.Environment environment) { - return environment.equals(World.Environment.NETHER) ? - this.plugin.getIWM().getNetherWorld(overWorld) : - this.plugin.getIWM().getEndWorld(overWorld); + return switch (environment) { + case NETHER -> this.plugin.getIWM().getNetherWorld(overWorld); + case THE_END -> this.plugin.getIWM().getEndWorld(overWorld); + default -> Util.getWorld(overWorld); + }; } @@ -160,7 +173,11 @@ public abstract class AbstractTeleportListener */ protected boolean hasPartnerIsland(Island island, World.Environment environment) { - return environment.equals(World.Environment.NETHER) ? island.hasNetherIsland() : island.hasEndIsland(); + return switch (environment) { + case NETHER -> island.hasNetherIsland(); + case THE_END -> island.hasEndIsland(); + default -> true; + }; } From bf87cca7543ef0a7fefe1241bd5e6075e71f7f1e Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 30 Sep 2022 21:33:17 +0300 Subject: [PATCH 71/74] Implement proper entity teleportation between dimension. Teleportation via portals for entities was in a mixed state. It was not fully implemented and not fully prevented. Especially when portal linking was enabled. Now I implemented world settings flag: ENTITY_PORTAL_TELEPORT. Enabling this flag will allow entities to use portals to switch dimensions. Fixes #2023 and #966 --- .../world/bentobox/bentobox/BentoBox.java | 3 + .../teleports/EntityTeleportListener.java | 412 ++++++++++++++++++ .../world/bentobox/bentobox/lists/Flags.java | 7 + src/main/resources/locales/en-US.yml | 6 + 4 files changed, 428 insertions(+) create mode 100644 src/main/java/world/bentobox/bentobox/listeners/teleports/EntityTeleportListener.java diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java index dd004a790..04bfc548d 100644 --- a/src/main/java/world/bentobox/bentobox/BentoBox.java +++ b/src/main/java/world/bentobox/bentobox/BentoBox.java @@ -27,6 +27,7 @@ import world.bentobox.bentobox.listeners.BlockEndDragon; import world.bentobox.bentobox.listeners.DeathListener; import world.bentobox.bentobox.listeners.JoinLeaveListener; import world.bentobox.bentobox.listeners.PanelListenerManager; +import world.bentobox.bentobox.listeners.teleports.EntityTeleportListener; import world.bentobox.bentobox.listeners.teleports.PlayerTeleportListener; import world.bentobox.bentobox.listeners.StandardSpawnProtectionListener; import world.bentobox.bentobox.managers.AddonsManager; @@ -289,6 +290,8 @@ public class BentoBox extends JavaPlugin { manager.registerEvents(new StandardSpawnProtectionListener(this), this); // Player portals manager.registerEvents(new PlayerTeleportListener(this), this); + // Entity portals + manager.registerEvents(new EntityTeleportListener(this), this); // End dragon blocking manager.registerEvents(new BlockEndDragon(this), this); // Banned visitor commands diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/EntityTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/EntityTeleportListener.java new file mode 100644 index 000000000..6461065ec --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/EntityTeleportListener.java @@ -0,0 +1,412 @@ +// +// Created by BONNe +// Copyright - 2022 +// + + +package world.bentobox.bentobox.listeners.teleports; + + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityPortalEnterEvent; +import org.bukkit.event.entity.EntityPortalEvent; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.NonNull; + +import java.util.UUID; + +import io.papermc.paper.event.entity.EntityMoveEvent; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.lists.Flags; +import world.bentobox.bentobox.util.Util; +import world.bentobox.bentobox.util.teleport.ClosestSafeSpotTeleport; + + +/** + * This class handles entity teleportation between dimensions. + * + * @author BONNe + */ +public class EntityTeleportListener extends AbstractTeleportListener implements Listener +{ + /** + * Instance of Teleportation processor. + * + * @param bentoBox BentoBox plugin. + */ + public EntityTeleportListener(@NonNull BentoBox bentoBox) + { + super(bentoBox); + } + + + /** + * This listener checks entity portal events and triggers appropriate methods to transfer + * entities to the correct location in other dimension. + * + * This event is triggered when entity is about to being teleported because of contact with the + * nether portal or end gateway portal (exit portal triggers respawn). + * + * This event is not called if nether/end is disabled in server settings. + * + * @param event the entity portal event. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onEntityPortal(EntityPortalEvent event) + { + World fromWorld = event.getFrom().getWorld(); + World overWorld = Util.getWorld(fromWorld); + + if (overWorld == null || !this.plugin.getIWM().inWorld(overWorld) || event.getTo() == null) + { + // Not a bentobox world. + return; + } + + if (!Flags.ENTITY_PORTAL_TELEPORT.isSetForWorld(overWorld)) + { + // Teleportation is disabled. Cancel event. + event.setCancelled(true); + } + + // Trigger event processor. + this.portalProcess(event, event.getTo().getWorld().getEnvironment()); + } + + + /** + * Fires the event if nether or end is disabled at the system level + * + * @param event - EntityPortalEnterEvent + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onEntityEnterPortal(EntityPortalEnterEvent event) + { + if (EntityType.PLAYER.equals(event.getEntity().getType())) + { + // This handles only non-players. + return; + } + + Entity entity = event.getEntity(); + Material type = event.getLocation().getBlock().getType(); + UUID uuid = entity.getUniqueId(); + + if (this.inPortal.contains(uuid)) + { + // Already in process. + return; + } + + World fromWorld = event.getLocation().getWorld(); + World overWorld = Util.getWorld(fromWorld); + + if (overWorld == null || !this.plugin.getIWM().inWorld(overWorld)) + { + // Not a bentobox world. + return; + } + + if (!Flags.ENTITY_PORTAL_TELEPORT.isSetForWorld(overWorld)) + { + // Teleportation is disabled. Cancel processing. + return; + } + + this.inPortal.add(uuid); + // Add original world for respawning. + this.teleportOrigin.put(uuid, fromWorld); + + // Entities are teleported instantly. + if (!Bukkit.getAllowNether() && type.equals(Material.NETHER_PORTAL)) + { + if (fromWorld == overWorld) + { + this.portalProcess( + new EntityPortalEvent(entity, event.getLocation(), event.getLocation(), 0), + World.Environment.NETHER); + } + else + { + this.portalProcess( + new EntityPortalEvent(entity, event.getLocation(), event.getLocation(), 0), + World.Environment.NORMAL); + } + + // Do not process anything else. + return; + } + + // Entities are teleported instantly. + if (!Bukkit.getAllowEnd() && (type.equals(Material.END_PORTAL) || type.equals(Material.END_GATEWAY))) + { + if (fromWorld == this.getNetherEndWorld(overWorld, World.Environment.THE_END)) + { + this.portalProcess( + new EntityPortalEvent(entity, event.getLocation(), event.getLocation(), 0), + World.Environment.NORMAL); + } + else + { + this.portalProcess( + new EntityPortalEvent(entity, event.getLocation(), event.getLocation(), 0), + World.Environment.THE_END); + } + } + } + + + /** + * Remove inPortal flag only when entity exits the portal + * + * @param event entity move event + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onEntityExitPortal(EntityMoveEvent event) + { + if (!this.inPortal.contains(event.getEntity().getUniqueId())) + { + return; + } + + if (!event.getTo().getBlock().getType().equals(Material.NETHER_PORTAL)) + { + // Player exits nether portal. + this.inPortal.remove(event.getEntity().getUniqueId()); + this.inTeleport.remove(event.getEntity().getUniqueId()); + this.teleportOrigin.remove(event.getEntity().getUniqueId()); + } + } + + +// --------------------------------------------------------------------- +// Section: Methods +// --------------------------------------------------------------------- + + + /** + * This method process entity teleportation to a correct dimension. + * @param event Event that triggers teleportation. + * @param environment Environment of the dimension where entity must appear. + */ + private void portalProcess(EntityPortalEvent event, World.Environment environment) + { + World fromWorld = event.getFrom().getWorld(); + World overWorld = Util.getWorld(fromWorld); + + if (fromWorld == null || overWorld == null) + { + // Missing worlds. + event.setCancelled(true); + return; + } + + if (!this.isAllowedInConfig(overWorld, environment)) + { + // World is disabled in config. Do not teleport player. + event.setCancelled(true); + return; + } + + if (!this.isAllowedOnServer(environment)) + { + // World is disabled in bukkit. Event is not triggered, but cancel by chance. + event.setCancelled(true); + } + + if (this.inTeleport.contains(event.getEntity().getUniqueId())) + { + // Entity is already in teleportation. + return; + } + + this.inTeleport.add(event.getEntity().getUniqueId()); + + // Get target world. + World toWorld; + + if (environment.equals(World.Environment.NORMAL)) + { + toWorld = overWorld; + } + else + { + toWorld = this.getNetherEndWorld(overWorld, environment); + } + + if (!overWorld.equals(toWorld) && !this.isIslandWorld(overWorld, environment)) + { + // This is not island world. Use standard nether or end world teleportation. + this.handleToStandardNetherOrEnd(event, overWorld, toWorld); + return; + } + + if (!overWorld.equals(fromWorld) && !this.isIslandWorld(overWorld, environment)) + { + // If entering a portal in the other world, teleport to a portal in overworld if + // there is one + this.handleFromStandardNetherOrEnd(event, overWorld, toWorld.getEnvironment()); + return; + } + + // Set the destination location + // If portals cannot be created, then destination is the spawn point, otherwise it's the vector + event.setTo(this.calculateLocation(event.getFrom(), + fromWorld, + toWorld, + environment, + this.isMakePortals(overWorld, environment))); + + // Calculate search radius for portal + this.getIsland(event.getTo()).ifPresent(island -> + event.setSearchRadius(this.calculateSearchRadius(event.getTo(), island))); + + // Check if there is an island there or not + if (this.isPastingMissingIslands(overWorld) && + this.isAllowedInConfig(overWorld, environment) && + this.isIslandWorld(overWorld, environment) && + this.getNetherEndWorld(overWorld, environment) != null && + this.getIsland(event.getTo()). + filter(island -> !this.hasPartnerIsland(island, environment)). + map(island -> { + event.setCancelled(true); + return true; + }). + orElse(false)) + { + // If there is no island, then processor already entity cannot be teleported before player + // visit that dimension. + return; + } + + if (!event.isCancelled()) + { + // Let the server teleport + return; + } + + if (environment.equals(World.Environment.THE_END)) + { + // Prevent death from hitting the ground while calculating location. + event.getEntity().setVelocity(new Vector(0,0,0)); + event.getEntity().setFallDistance(0); + } + + // If we do not generate portals, teleportation should happen manually with safe spot builder. + // Otherwise, we could end up with situations when player is placed in mid air, if teleportation + // is done instantly. + // Our safe spot task is triggered in next tick, however, end teleportation happens in the same tick. + // It is placed outside THE_END check, as technically it could happen with the nether portal too. + + // If there is a portal to go to already, then the player will go there + Bukkit.getScheduler().runTask(this.plugin, () -> { + if (!event.getEntity().getWorld().equals(toWorld)) + { + // Else manually teleport entity + ClosestSafeSpotTeleport.builder(this.plugin). + entity(event.getEntity()). + location(event.getTo()). + portal(). + successRunnable(() -> { + // Reset velocity just in case. + event.getEntity().setVelocity(new Vector(0,0,0)); + event.getEntity().setFallDistance(0); + }). + build(); + } + }); + } + + + /** + * Handle teleport to standard nether or end + * @param event - EntityPortalEvent + * @param overWorld - over world + * @param toWorld - to world + */ + private void handleToStandardNetherOrEnd(EntityPortalEvent event, World overWorld, World toWorld) + { + Location spawnPoint = toWorld.getSpawnLocation(); + + // If going to the nether and nether portals are active then just teleport to approx location + if (World.Environment.NETHER.equals(toWorld.getEnvironment()) && + this.plugin.getIWM().getWorldSettings(overWorld).isMakeNetherPortals()) + { + spawnPoint = event.getFrom().toVector().toLocation(toWorld); + } + + // If spawn is set as 0,63,0 in the End then move it to 100, 50 ,0. + if (World.Environment.THE_END.equals(toWorld.getEnvironment()) && spawnPoint.getBlockX() == 0 && spawnPoint.getBlockZ() == 0) + { + // Set to the default end spawn + spawnPoint = new Location(toWorld, 100, 50, 0); + toWorld.setSpawnLocation(100, 50, 0); + } + + if (this.isAllowedOnServer(toWorld.getEnvironment())) + { + // To Standard Nether or end + event.setTo(spawnPoint); + } + else + { + // Teleport to standard nether or end + ClosestSafeSpotTeleport.builder(this.plugin). + entity(event.getEntity()). + location(spawnPoint). + portal(). + build(); + } + } + + + /** + * Handle teleport from standard nether or end + * @param event - EntityPortalEvent + * @param overWorld - over world + * @param environment - to world environment + */ + private void handleFromStandardNetherOrEnd(EntityPortalEvent event, World overWorld, World.Environment environment) + { + if (World.Environment.NETHER.equals(environment) && + this.plugin.getIWM().getWorldSettings(overWorld).isMakeNetherPortals()) + { + // Set to location directly to the from location. + event.setTo(event.getFrom().toVector().toLocation(overWorld)); + + // Update portal search radius. + this.getIsland(event.getTo()).ifPresent(island -> + event.setSearchRadius(this.calculateSearchRadius(event.getTo(), island))); + } + else + { + // Cannot be portal. Should recalculate position. + Location spawnLocation = this.getSpawnLocation(overWorld); + + event.setTo(spawnLocation == null ? + event.getFrom().toVector().toLocation(overWorld) : + spawnLocation); + } + + if (!this.isAllowedOnServer(environment)) + { + // Custom portal handling. + event.setCancelled(true); + + // Teleport to standard nether or end + ClosestSafeSpotTeleport.builder(this.plugin). + entity(event.getEntity()). + location(event.getTo()). + portal(). + build(); + } + } +} diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index ac5ce1a6a..3a1dc52b6 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -570,6 +570,13 @@ public final class Flags { */ public static final Flag VISITOR_TRIGGER_RAID = new Flag.Builder("VISITOR_TRIGGER_RAID", Material.RAVAGER_SPAWN_EGG).listener(new VisitorsStartingRaidListener()).type(Type.WORLD_SETTING).defaultSetting(true).build(); + /** + * Toggles whether entities can teleport between dimensions using portals. + * @since 1.21.0 + * @see world.bentobox.bentobox.listeners.teleports.EntityTeleportListener + */ + public static final Flag ENTITY_PORTAL_TELEPORT = new Flag.Builder("ENTITY_PORTAL_TELEPORT", Material.ENDER_EYE).type(Type.WORLD_SETTING).defaultSetting(false).build(); + /** * Provides a list of all the Flag instances contained in this class using reflection. * Deprecated Flags are ignored. diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 0dbc062cd..afdb3c96f 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1370,6 +1370,12 @@ protection: &a visiting. &a &a Bad Omen effect will be removed! + ENTITY_PORTAL_TELEPORT: + name: "Entity portal usage" + description: |- + &a Toggles if entities (non-player) can + &a use portals to teleport between + &a dimensions WITHER_DAMAGE: name: "Toggle wither damage" description: |- From f01e5540c7e6a48f8f998932e1d9a269c4619bdf Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 30 Sep 2022 21:41:02 +0300 Subject: [PATCH 72/74] Deprecate old player teleportation code. --- .../bentobox/bentobox/listeners/PlayerEntityPortalEvent.java | 2 +- .../bentobox/listeners/PortalTeleportationListener.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/PlayerEntityPortalEvent.java b/src/main/java/world/bentobox/bentobox/listeners/PlayerEntityPortalEvent.java index f1c29abc3..a1bd8f07d 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/PlayerEntityPortalEvent.java +++ b/src/main/java/world/bentobox/bentobox/listeners/PlayerEntityPortalEvent.java @@ -17,7 +17,7 @@ import world.bentobox.bentobox.database.objects.Island; /** * Abstracts PlayerPortalEvent and EntityPortalEvent * @author tastybento - * + * @deprecated replaced not used in new listeners. */ public class PlayerEntityPortalEvent { diff --git a/src/main/java/world/bentobox/bentobox/listeners/PortalTeleportationListener.java b/src/main/java/world/bentobox/bentobox/listeners/PortalTeleportationListener.java index 28a56a8f1..7d0ae42a4 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/PortalTeleportationListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/PortalTeleportationListener.java @@ -40,7 +40,11 @@ import world.bentobox.bentobox.util.teleport.SafeSpotTeleport; * Handles teleportation via the Nether/End portals to the Nether and End dimensions of the worlds added by the GameModeAddons. * * @author tastybento + * @deprecated replaced by better listeners. + * @see world.bentobox.bentobox.listeners.teleports.PlayerTeleportListener + * @see world.bentobox.bentobox.listeners.teleports.EntityTeleportListener */ +@Deprecated public class PortalTeleportationListener implements Listener { private final BentoBox plugin; From 52bca661499254d0e70aa3ceea9320a11794b28b Mon Sep 17 00:00:00 2001 From: BONNe Date: Fri, 30 Sep 2022 21:46:01 +0300 Subject: [PATCH 73/74] Add better config description for safe-spot-search-range. --- src/main/java/world/bentobox/bentobox/Settings.java | 4 +++- src/main/resources/config.yml | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/Settings.java b/src/main/java/world/bentobox/bentobox/Settings.java index f91102324..b539fbc63 100644 --- a/src/main/java/world/bentobox/bentobox/Settings.java +++ b/src/main/java/world/bentobox/bentobox/Settings.java @@ -314,7 +314,9 @@ public class Settings implements ConfigObject { @ConfigEntry(path = "island.safe-spot-search-vertical-range", since = "1.19.1") private int safeSpotSearchVerticalRange = 400; - @ConfigComment("By default, If the destination is not safe, the plugin will try to search for a safe spot around the destination,") + @ConfigComment("By default, if the destination is not safe, the plugin will try to search for a safe spot around the destination.") + @ConfigComment("This allows to change the distance for searching this spot. Larger value will mean longer position search.") + @ConfigComment("This value is also used for valid nether portal linking between dimension.") @ConfigEntry(path = "island.safe-spot-search-range", since = "1.21.0") private int safeSpotSearchRange = 16; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 6f7cebe81..3ee16bc2f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -209,6 +209,11 @@ island: # If set to 0 or lower, the plugin will not expand the y-coordinate. # Added since 1.19.1. safe-spot-search-vertical-range: 400 + # By default, if the destination is not safe, the plugin will try to search for a safe spot around the destination. + # This allows to change the distance for searching this spot. Larger value will mean longer position search. + # This value is also used for valid nether portal linking between dimension. + # Added since 1.21.0. + safe-spot-search-range: 16 web: github: # Toggle whether BentoBox can connect to GitHub to get data about updates and addons. From b263c92c130cfe2b9ec7ce65f50735d2539ff2d6 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sat, 1 Oct 2022 01:50:32 +0300 Subject: [PATCH 74/74] Use Spigot only events. (#2029) I missed that EntityMoveEvent is only Paper. Interesting why Spigot does not have such... Fixes #2028 --- .../teleports/EntityTeleportListener.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/EntityTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/EntityTeleportListener.java index 6461065ec..f03ade703 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/teleports/EntityTeleportListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/EntityTeleportListener.java @@ -18,12 +18,12 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityPortalEnterEvent; import org.bukkit.event.entity.EntityPortalEvent; +import org.bukkit.event.entity.EntityPortalExitEvent; import org.bukkit.util.Vector; import org.eclipse.jdt.annotation.NonNull; import java.util.UUID; -import io.papermc.paper.event.entity.EntityMoveEvent; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.lists.Flags; import world.bentobox.bentobox.util.Util; @@ -170,20 +170,16 @@ public class EntityTeleportListener extends AbstractTeleportListener implements * @param event entity move event */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onEntityExitPortal(EntityMoveEvent event) + public void onEntityExitPortal(EntityPortalExitEvent event) { if (!this.inPortal.contains(event.getEntity().getUniqueId())) { return; } - if (!event.getTo().getBlock().getType().equals(Material.NETHER_PORTAL)) - { - // Player exits nether portal. - this.inPortal.remove(event.getEntity().getUniqueId()); - this.inTeleport.remove(event.getEntity().getUniqueId()); - this.teleportOrigin.remove(event.getEntity().getUniqueId()); - } + this.inPortal.remove(event.getEntity().getUniqueId()); + this.inTeleport.remove(event.getEntity().getUniqueId()); + this.teleportOrigin.remove(event.getEntity().getUniqueId()); }