From bb9ed871755bc38cc033f9a85923cd40d5ba806f Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 1 Mar 2024 21:39:18 -0800 Subject: [PATCH 01/24] WIP --- .../admin/team/AdminTeamSetownerCommand.java | 48 +++++++++++++++---- .../bentobox/managers/IslandsManager.java | 6 +-- src/main/resources/locales/en-US.yml | 2 + 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java index c812cb789..d0ef29b61 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java @@ -1,9 +1,14 @@ package world.bentobox.bentobox.api.commands.admin.team; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.UUID; +import org.eclipse.jdt.annotation.Nullable; + import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.commands.ConfirmableCommand; import world.bentobox.bentobox.api.events.island.IslandEvent; import world.bentobox.bentobox.api.events.team.TeamEvent; import world.bentobox.bentobox.api.localization.TextVariables; @@ -17,7 +22,11 @@ import world.bentobox.bentobox.util.Util; * * @author tastybento */ -public class AdminTeamSetownerCommand extends CompositeCommand { +public class AdminTeamSetownerCommand extends ConfirmableCommand { + + private @Nullable UUID targetUUID; + private Island island; + private @Nullable UUID previousOwnerUUID; public AdminTeamSetownerCommand(CompositeCommand parent) { super(parent, "setowner"); @@ -28,17 +37,19 @@ public class AdminTeamSetownerCommand extends CompositeCommand { setPermission("mod.team.setowner"); setParametersHelp("commands.admin.team.setowner.parameters"); setDescription("commands.admin.team.setowner.description"); + this.setOnlyPlayer(true); } @Override - public boolean execute(User user, String label, List args) { + public boolean canExecute(User user, String label, List args) { // If args are not right, show help if (args.size() != 1) { showHelp(this, user); return false; } + // Get target - UUID targetUUID = Util.getUUID(args.get(0)); + targetUUID = Util.getUUID(args.get(0)); if (targetUUID == null) { user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); return false; @@ -47,16 +58,33 @@ public class AdminTeamSetownerCommand extends CompositeCommand { user.sendMessage("general.errors.not-in-team"); return false; } - Island island = getIslands().getPrimaryIsland(getWorld(), targetUUID); - UUID previousOwnerUUID = island.getOwner(); + // Check that user is on an island + Optional opIsland = getIslands().getIslandAt(user.getLocation()); + if (opIsland.isEmpty()) { + user.sendMessage("commands.admin.team.setowner.must-be-on-island"); + return false; + } + island = opIsland.get(); + previousOwnerUUID = island.getOwner(); if (targetUUID.equals(previousOwnerUUID)) { user.sendMessage("commands.admin.team.setowner.already-owner", TextVariables.NAME, args.get(0)); return false; } + return true; + } - // Get the User corresponding to the current owner + public boolean execute(User user, String label, List args) { + Objects.requireNonNull(island); + Objects.requireNonNull(targetUUID); + + this.askConfirmation(user, user.getTranslation("commands.admin.team.setowner.confirmation", TextVariables.NAME, + args.get(0), TextVariables.XYZ, Util.xyz(island.getCenter().toVector())), () -> changeOwner(user)); + return true; + + } + + private void changeOwner(User user) { User target = User.getInstance(targetUUID); - // Fire event so add-ons know // Call the setowner event TeamEvent.builder().island(island).reason(TeamEvent.Reason.SETOWNER).involvedPlayer(targetUUID).admin(true) @@ -70,8 +98,8 @@ public class AdminTeamSetownerCommand extends CompositeCommand { .build(); // Make new owner - getIslands().setOwner(getWorld(), user, targetUUID); - user.sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, args.get(0)); + getIslands().setOwner(user, targetUUID, island, RanksManager.MEMBER_RANK); + user.sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, target.getName()); // Call the rank change event for the old island owner if (previousOwnerUUID != null) { @@ -80,6 +108,6 @@ public class AdminTeamSetownerCommand extends CompositeCommand { .reason(IslandEvent.Reason.RANK_CHANGE) .rankChange(RanksManager.OWNER_RANK, island.getRank(previousOwnerUUID)).build(); } - return true; + } } diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 08a257c15..0ed4c2ab1 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -1520,7 +1520,7 @@ public class IslandsManager { /** * Sets this target as the owner for this island * - * @param user previous owner + * @param user user making the change * @param targetUUID new owner * @param island island to register * @param rank rank to which to set old owner. @@ -1528,8 +1528,8 @@ public class IslandsManager { public void setOwner(User user, UUID targetUUID, Island island, int rank) { islandCache.setOwner(island, targetUUID); // Set old owner as sub-owner on island. - if (rank > RanksManager.VISITOR_RANK) { - island.setRank(user, rank); + if (rank > RanksManager.VISITOR_RANK && island.getOwner() != null) { + island.setRank(island.getOwner(), rank); } user.sendMessage("commands.island.team.setowner.name-is-the-owner", "[name]", diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index ac0a963ab..a10f99ed0 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -144,6 +144,8 @@ commands: parameters: description: transfers island ownership to the player already-owner: '&c [name] is already the owner of this island!' + must-be-on-island: '&c You must be on the island to set the owner' + confirmation: '&a Are you sure you want to set [name] to be the owner of the island at [xyz]?' success: '&b [name]&a is now the owner of this island.' range: description: admin island range command From 7d52325196b39ff1e8e9ab792b5cdee4864ea0b8 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 2 Mar 2024 19:23:31 -0800 Subject: [PATCH 02/24] Version 2.1.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 49c1bd3c9..e3c793e45 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ -LOCAL - 2.1.1 + 2.1.2 bentobox-world https://sonarcloud.io ${project.basedir}/lib From 19d81c70c63bae1f8dd9ddfeccf6c08499a6aed1 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 2 Mar 2024 19:29:26 -0800 Subject: [PATCH 03/24] Fix for Island team kick requires confirmation when using GUI #2311 --- .../api/commands/island/team/IslandTeamCommand.java | 2 +- .../api/commands/island/team/IslandTeamKickCommand.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java index 97e65eeda..ee8d33f59 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java @@ -557,7 +557,7 @@ public class IslandTeamCommand extends CompositeCommand { case RanksManager.TRUSTED_RANK -> this.unTrustCommand.unTrustCmd(user, member.getUniqueId()); default -> { if (kickCommand.canExecute(user, kickCommand.getLabel(), List.of(member.getName()))) { - yield kickCommand.execute(user, kickCommand.getLabel(), List.of(member.getName())); + yield kickCommand.kick(clicker, member.getUniqueId()); } else { yield false; } 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 38989a433..465956d4b 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 @@ -90,7 +90,10 @@ public class IslandTeamKickCommand extends ConfirmableCommand { } } - protected void kick(User user, UUID targetUUID) { + protected boolean kick(User user, UUID targetUUID) { + if (targetUUID == null) { + return false; + } User target = User.getInstance(targetUUID); Island oldIsland = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID)); // Should never be // null because of @@ -99,7 +102,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand { IslandBaseEvent event = TeamEvent.builder().island(oldIsland).reason(TeamEvent.Reason.KICK) .involvedPlayer(targetUUID).build(); if (event.isCancelled()) { - return; + return false; } target.sendMessage("commands.island.team.kick.player-kicked", TextVariables.GAMEMODE, getAddon().getDescription().getName(), TextVariables.NAME, user.getName(), TextVariables.DISPLAY_NAME, @@ -121,6 +124,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand { getParent().getSubCommand("invite").ifPresent(c -> c.setCooldown(oldIsland.getUniqueId(), targetUUID.toString(), getSettings().getInviteCooldown() * 60)); } + return true; } @Override From 24c68a0d955164f47bf1fd11649596e53dafcf7a Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 3 Mar 2024 16:07:49 -0800 Subject: [PATCH 04/24] Rewrite of admin setowner command #2309 --- .../admin/team/AdminTeamSetownerCommand.java | 28 ++- .../bentobox/managers/IslandsManager.java | 9 +- .../bentobox/managers/island/IslandCache.java | 2 + src/main/resources/locales/en-US.yml | 1 + .../team/AdminTeamSetownerCommandTest.java | 176 +++++++++++------- 5 files changed, 139 insertions(+), 77 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java index d0ef29b61..71f9a69d0 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommand.java @@ -5,8 +5,11 @@ import java.util.Objects; import java.util.Optional; import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.ConfirmableCommand; import world.bentobox.bentobox.api.events.island.IslandEvent; @@ -54,10 +57,6 @@ public class AdminTeamSetownerCommand extends ConfirmableCommand { user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); return false; } - if (!getIslands().inTeam(getWorld(), targetUUID)) { - user.sendMessage("general.errors.not-in-team"); - return false; - } // Check that user is on an island Optional opIsland = getIslands().getIslandAt(user.getLocation()); if (opIsland.isEmpty()) { @@ -83,7 +82,7 @@ public class AdminTeamSetownerCommand extends ConfirmableCommand { } - private void changeOwner(User user) { + protected void changeOwner(User user) { User target = User.getInstance(targetUUID); // Fire event so add-ons know // Call the setowner event @@ -101,6 +100,18 @@ public class AdminTeamSetownerCommand extends ConfirmableCommand { getIslands().setOwner(user, targetUUID, island, RanksManager.MEMBER_RANK); user.sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, target.getName()); + // Report if this made player have more islands than expected + // Get how many islands this player has + int num = this.getIslands().getNumberOfConcurrentIslands(targetUUID, getWorld()); + int max = target.getPermissionValue( + this.getIWM().getAddon(getWorld()).map(GameModeAddon::getPermissionPrefix).orElse("") + "island.number", + this.getIWM().getWorldSettings(getWorld()).getConcurrentIslands()); + if (num > max) { + // You cannot make an island + user.sendMessage("commands.admin.team.setowner.extra-islands", TextVariables.NUMBER, String.valueOf(num), + "[max]", String.valueOf(max)); + } + // Call the rank change event for the old island owner if (previousOwnerUUID != null) { // We need to call it AFTER the actual change. @@ -110,4 +121,11 @@ public class AdminTeamSetownerCommand extends ConfirmableCommand { } } + + @Override + public Optional> tabComplete(User user, String alias, List args) { + String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : ""; + List options = Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(); + return Optional.of(Util.tabLimit(options, lastArg)); + } } diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 0ed4c2ab1..fff6cd23a 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -1526,11 +1526,16 @@ public class IslandsManager { * @param rank rank to which to set old owner. */ public void setOwner(User user, UUID targetUUID, Island island, int rank) { - islandCache.setOwner(island, targetUUID); - // Set old owner as sub-owner on island. + // Demote the old owner + if (rank >= RanksManager.OWNER_RANK) { + plugin.logWarning("Setowner: previous owner's rank cannot be higher than SubOwner"); + rank = RanksManager.SUB_OWNER_RANK; + } if (rank > RanksManager.VISITOR_RANK && island.getOwner() != null) { island.setRank(island.getOwner(), rank); } + // Make the new owner + islandCache.setOwner(island, targetUUID); user.sendMessage("commands.island.team.setowner.name-is-the-owner", "[name]", plugin.getPlayers().getName(targetUUID)); diff --git a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java index 5e6896348..360e3b023 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.flags.Flag; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Util; /** @@ -353,6 +354,7 @@ public class IslandCache { if (newOwnerUUID != null) { islandsByUUID.computeIfAbsent(newOwnerUUID, k -> new HashSet<>()).add(island); } + island.setRank(newOwnerUUID, RanksManager.OWNER_RANK); islandsByLocation.put(island.getCenter(), island); islandsById.put(island.getUniqueId(), island); } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index a10f99ed0..2bbbd78bb 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -147,6 +147,7 @@ commands: must-be-on-island: '&c You must be on the island to set the owner' confirmation: '&a Are you sure you want to set [name] to be the owner of the island at [xyz]?' success: '&b [name]&a is now the owner of this island.' + extra-islands: '&c Warning: this player now owns [number] islands. This is more than allowed by settings or perms: [max].' range: description: admin island range command invalid-value: diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommandTest.java index ade500c35..86ae24bac 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamSetownerCommandTest.java @@ -1,38 +1,45 @@ package world.bentobox.bentobox.api.commands.admin.team; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.Optional; import java.util.UUID; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.NonNull; import org.junit.After; import org.junit.Before; import org.junit.Test; 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.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.Settings; +import world.bentobox.bentobox.TestWorldSettings; import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.configuration.WorldSettings; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; @@ -40,6 +47,7 @@ import world.bentobox.bentobox.managers.CommandsManager; import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.IslandsManager; import world.bentobox.bentobox.managers.LocalesManager; +import world.bentobox.bentobox.managers.PlaceholdersManager; import world.bentobox.bentobox.managers.PlayersManager; import world.bentobox.bentobox.util.Util; @@ -53,16 +61,19 @@ public class AdminTeamSetownerCommandTest { @Mock private CompositeCommand ac; - private UUID uuid; + private UUID uuid = UUID.randomUUID(); @Mock private User user; @Mock private IslandsManager im; @Mock private PlayersManager pm; - private UUID notUUID; + private UUID notUUID = UUID.randomUUID(); @Mock private Island island; + private AdminTeamSetownerCommand itl; + @Mock + private @NonNull Location location; /** */ @@ -73,35 +84,57 @@ public class AdminTeamSetownerCommandTest { Whitebox.setInternalState(BentoBox.class, "instance", plugin); Util.setPlugin(plugin); + Settings settings = new Settings(); + // Settings + when(plugin.getSettings()).thenReturn(settings); + // Command manager CommandsManager cm = mock(CommandsManager.class); when(plugin.getCommandsManager()).thenReturn(cm); // Player Player p = mock(Player.class); + when(p.getUniqueId()).thenReturn(uuid); + when(p.getName()).thenReturn("tastybento"); + User.getInstance(p); // Sometimes use Mockito.withSettings().verboseLogging() when(user.isOp()).thenReturn(false); - uuid = UUID.randomUUID(); - notUUID = UUID.randomUUID(); - while (notUUID.equals(uuid)) { - notUUID = UUID.randomUUID(); - } when(user.getUniqueId()).thenReturn(uuid); when(user.getPlayer()).thenReturn(p); when(user.getName()).thenReturn("tastybento"); + when(user.getTranslation(anyString())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); + when(user.getTranslation(anyString(), anyString(), anyString(), anyString(), anyString())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); User.setPlugin(plugin); + // Locales & Placeholders + LocalesManager lm = mock(LocalesManager.class); + when(lm.get(any(), any())).thenAnswer((Answer) invocation -> invocation.getArgument(1, String.class)); + PlaceholdersManager phm = mock(PlaceholdersManager.class); + when(plugin.getPlaceholdersManager()).thenReturn(phm); + when(phm.replacePlaceholders(any(), any())) + .thenAnswer((Answer) invocation -> invocation.getArgument(1, String.class)); + + when(plugin.getLocalesManager()).thenReturn(lm); + // Parent command has no aliases when(ac.getSubCommandAliases()).thenReturn(new HashMap<>()); // Island World Manager IslandWorldManager iwm = mock(IslandWorldManager.class); when(plugin.getIWM()).thenReturn(iwm); + @NonNull + WorldSettings worldSettings = new TestWorldSettings(); + when(iwm.getWorldSettings(any())).thenReturn(worldSettings); + // Location + when(location.toVector()).thenReturn(new Vector(1, 2, 3)); // Player has island to begin with when(im.hasIsland(any(), any(UUID.class))).thenReturn(true); when(im.hasIsland(any(), any(User.class))).thenReturn(true); when(island.getOwner()).thenReturn(uuid); + when(island.getCenter()).thenReturn(location); when(im.getPrimaryIsland(any(), any())).thenReturn(island); when(plugin.getIslands()).thenReturn(im); @@ -115,15 +148,13 @@ public class AdminTeamSetownerCommandTest { PowerMockito.mockStatic(Bukkit.class); when(Bukkit.getScheduler()).thenReturn(sch); - // Locales - LocalesManager lm = mock(LocalesManager.class); - when(lm.get(any(), any())).thenReturn("mock translation"); - when(plugin.getLocalesManager()).thenReturn(lm); - // Plugin Manager PluginManager pim = mock(PluginManager.class); when(Bukkit.getPluginManager()).thenReturn(pim); + // DUT + itl = new AdminTeamSetownerCommand(ac); + } @After @@ -133,56 +164,45 @@ public class AdminTeamSetownerCommandTest { } /** - * Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}. + * Test method for {@link AdminTeamSetownerCommand#canExecute(User, String, List)}. */ @Test public void testExecuteNoTarget() { - AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac); - assertFalse(itl.execute(user, itl.getLabel(), new ArrayList<>())); + assertFalse(itl.canExecute(user, itl.getLabel(), new ArrayList<>())); // Show help + verify(user).sendMessage("commands.help.header", TextVariables.LABEL, "commands.help.console"); } /** - * Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}. + * Test method for {@link AdminTeamSetownerCommand#setup()} + */ + @Test + public void testSetup() { + assertEquals("commands.admin.team.setowner.description", itl.getDescription()); + assertEquals("commands.admin.team.setowner.parameters", itl.getParameters()); + assertTrue(itl.isOnlyPlayer()); + assertEquals("mod.team.setowner", itl.getPermission()); + } + + /** + * Test method for {@link AdminTeamSetownerCommand#canExecute(User, String, List)}. */ @Test public void testExecuteUnknownPlayer() { - AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac); - String[] name = { "tastybento" }; - when(pm.getUUID(any())).thenReturn(null); - assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name))); - verify(user).sendMessage("general.errors.unknown-player", "[name]", name[0]); + assertFalse(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); + verify(user).sendMessage("general.errors.unknown-player", "[name]", "tastybento"); } /** - * Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}. - */ - @Test - public void testExecutePlayerNotInTeam() { - AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac); - String[] name = { "tastybento" }; - when(pm.getUUID(any())).thenReturn(notUUID); - // when(im.getMembers(any(), any())).thenReturn(new HashSet<>()); - assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name))); - verify(user).sendMessage(eq("general.errors.not-in-team")); - } - - /** - * Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}. + * Test method for {@link AdminTeamSetownerCommand#canExecute(User, String, List)}. */ @Test public void testExecuteMakeOwnerAlreadyOwner() { - when(im.inTeam(any(), any())).thenReturn(true); - Island is = mock(Island.class); - when(im.getIsland(any(), any(UUID.class))).thenReturn(is); - String[] name = {"tastybento"}; - when(pm.getUUID(any())).thenReturn(notUUID); - when(pm.getName(any())).thenReturn(name[0]); - when(island.getOwner()).thenReturn(notUUID); - - AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac); - assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name))); - verify(user).sendMessage("commands.admin.team.setowner.already-owner", TextVariables.NAME, name[0]); + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + when(island.getOwner()).thenReturn(uuid); + when(Util.getUUID("tastybento")).thenReturn(uuid); + assertFalse(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); + verify(user).sendMessage("commands.admin.team.setowner.already-owner", TextVariables.NAME, "tastybento"); } /** @@ -190,28 +210,44 @@ public class AdminTeamSetownerCommandTest { */ @Test public void testExecuteSuccess() { - // Player is a team member, not an owner - when(im.hasIsland(any(), any(UUID.class))).thenReturn(false); - when(im.hasIsland(any(), any(User.class))).thenReturn(false); - when(im.inTeam(any(), any())).thenReturn(true); - Island is = mock(Island.class); - when(im.getIsland(any(), any(UUID.class))).thenReturn(is); - String[] name = {"tastybento"}; - when(pm.getUUID(any())).thenReturn(notUUID); - when(pm.getName(any())).thenReturn(name[0]); - // Owner - //when(im.getOwner(any(), eq(notUUID))).thenReturn(uuid); - when(pm.getName(eq(uuid))).thenReturn("owner"); - // Members - Set members = new HashSet<>(); - members.add(uuid); - members.add(notUUID); - //when(im.getMembers(any(), any())).thenReturn(members); + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + when(island.getOwner()).thenReturn(notUUID); + when(Util.getUUID("tastybento")).thenReturn(uuid); - AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac); - assertTrue(itl.execute(user, itl.getLabel(), Arrays.asList(name))); + assertTrue(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); + assertTrue(itl.execute(user, itl.getLabel(), List.of("tastybento"))); // Add other verifications - verify(im).setOwner(any(), eq(user), eq(notUUID)); - verify(user).sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, name[0]); + verify(user).getTranslation("commands.admin.team.setowner.confirmation", TextVariables.NAME, "tastybento", + TextVariables.XYZ, "1,2,3"); + } + + /** + * Test method for {@link AdminTeamSetownerCommand#changeOwner(User)} + */ + @Test + public void testChangeOwner() { + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + when(island.getOwner()).thenReturn(notUUID); + when(Util.getUUID("tastybento")).thenReturn(uuid); + + assertTrue(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); + itl.changeOwner(user); + // Add other verifications + verify(user).sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, "tastybento"); + } + + /** + * Test method for {@link AdminTeamSetownerCommand#changeOwner(User)} + */ + @Test + public void testChangeOwnerNoOwner() { + when(im.getIslandAt(any())).thenReturn(Optional.of(island)); + when(island.getOwner()).thenReturn(null); + when(Util.getUUID("tastybento")).thenReturn(uuid); + + assertTrue(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); + itl.changeOwner(user); + // Add other verifications + verify(user).sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, "tastybento"); } } From 57164dd846d13e9309eae1e4e42b019bbf6433f2 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 4 Mar 2024 07:01:47 -0800 Subject: [PATCH 05/24] Fixes #2313 --- .../listeners/flags/protection/InventoryListener.java | 4 ---- 1 file changed, 4 deletions(-) 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 a5d485678..1886c736a 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 @@ -28,7 +28,6 @@ import org.bukkit.inventory.BeaconInventory; import org.bukkit.inventory.BrewerInventory; import org.bukkit.inventory.CartographyInventory; import org.bukkit.inventory.ChiseledBookshelfInventory; -import org.bukkit.inventory.CraftingInventory; import org.bukkit.inventory.DoubleChestInventory; import org.bukkit.inventory.EnchantingInventory; import org.bukkit.inventory.FurnaceInventory; @@ -185,9 +184,6 @@ public class InventoryListener extends FlagListener } else if (e.getInventory() instanceof ChiseledBookshelfInventory) { this.checkIsland(e, player, e.getInventory().getLocation(), Flags.BOOKSHELF); return true; - } else if (e.getInventory() instanceof CraftingInventory) { - this.checkIsland(e, player, e.getInventory().getLocation(), Flags.CRAFTING); - return true; } else if (e.getInventory() instanceof DoubleChestInventory) { checkInvHolder(e.getInventory().getLocation(), e, player); return true; From cb7c63a5206c18f95edcaa0467afe2d9ee0409b2 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 9 Mar 2024 10:29:17 -0800 Subject: [PATCH 06/24] Version 2.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e3c793e45..997ff27cc 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ -LOCAL - 2.1.2 + 2.2.0 bentobox-world https://sonarcloud.io ${project.basedir}/lib From 4810c4c4ad10bf1595f5f09388e48c017535ad70 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 10 Mar 2024 10:40:26 -0700 Subject: [PATCH 07/24] Adds the ability to include MythicMobs in Blueprints. Fixes #2316 --- pom.xml | 12 ++ .../world/bentobox/bentobox/BentoBox.java | 4 + .../blueprints/BlueprintClipboard.java | 17 +- .../dataobjects/BlueprintEntity.java | 34 +++- .../bentobox/hooks/MythicMobsHook.java | 70 ++++++++ .../bentobox/util/DefaultPasteUtil.java | 58 +++--- src/main/resources/plugin.yml | 6 +- .../AdminBlueprintLoadCommandTest.java | 6 + .../AdminBlueprintSaveCommandTest.java | 11 +- .../blueprints/BlueprintClipboardTest.java | 8 + .../dataobjects/BlueprintEntityTest.java | 45 +++-- .../bentobox/hooks/MythicMobsHookTest.java | 165 ++++++++++++++++++ .../BlueprintClipboardManagerTest.java | 12 +- .../bentobox/util/DefaultPasteUtilTest.java | 55 +++++- 14 files changed, 455 insertions(+), 48 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/hooks/MythicMobsHook.java create mode 100644 src/test/java/world/bentobox/bentobox/hooks/MythicMobsHookTest.java diff --git a/pom.xml b/pom.xml index 997ff27cc..6169abb16 100644 --- a/pom.xml +++ b/pom.xml @@ -189,6 +189,12 @@ MG-Dev Jenkins CI Maven Repository https://ci.mg-dev.eu/plugin/repository/everything + + + nexus + Lumine Releases + https://mvn.lumine.io/repository/maven-public/ + @@ -297,6 +303,12 @@ ${myworlds.version} provided + + io.lumine + Mythic-Dist + 5.3.5 + provided + com.github.TheBusyBiscuit diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java index 1ee3cfb1e..235d37ef6 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.database.DatabaseSetup; import world.bentobox.bentobox.hooks.ItemsAdderHook; import world.bentobox.bentobox.hooks.MultiverseCoreHook; import world.bentobox.bentobox.hooks.MyWorldsHook; +import world.bentobox.bentobox.hooks.MythicMobsHook; import world.bentobox.bentobox.hooks.SlimefunHook; import world.bentobox.bentobox.hooks.VaultHook; import world.bentobox.bentobox.hooks.placeholders.PlaceholderAPIHook; @@ -185,6 +186,9 @@ public class BentoBox extends JavaPlugin implements Listener { final long enableStart = System.currentTimeMillis(); hooksManager.registerHook(new VaultHook()); + // MythicMobs + hooksManager.registerHook(new MythicMobsHook()); + hooksManager.registerHook(new PlaceholderAPIHook()); // Setup the Placeholders manager placeholdersManager = new PlaceholdersManager(this); diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java index a47f79571..048bfeec4 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java @@ -8,6 +8,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -43,6 +44,7 @@ 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.hooks.MythicMobsHook; /** * The clipboard provides the holding spot for an active blueprint that is being @@ -67,6 +69,7 @@ public class BlueprintClipboard { private final Map bpAttachable = new LinkedHashMap<>(); private final Map bpBlocks = new LinkedHashMap<>(); private final BentoBox plugin = BentoBox.getInstance(); + private Optional mmh; /** * Create a clipboard for blueprint @@ -74,9 +77,16 @@ public class BlueprintClipboard { */ public BlueprintClipboard(@NonNull Blueprint blueprint) { this.blueprint = blueprint; + // MythicMobs + mmh = plugin.getHooks().getHook("MythicMobs").filter(hook -> hook instanceof MythicMobsHook) + .map(h -> (MythicMobsHook) h); } - public BlueprintClipboard() { } + public BlueprintClipboard() { + // MythicMobs + mmh = plugin.getHooks().getHook("MythicMobs").filter(hook -> hook instanceof MythicMobsHook) + .map(h -> (MythicMobsHook) h); + } /** * Copy the blocks between pos1 and pos2 into the clipboard for a user. @@ -285,6 +295,7 @@ public class BlueprintClipboard { List bpEnts = new ArrayList<>(); for (LivingEntity entity: entities) { BlueprintEntity bpe = new BlueprintEntity(); + bpe.setType(entity.getType()); bpe.setCustomName(entity.getCustomName()); if (entity instanceof Villager villager) { @@ -317,6 +328,10 @@ public class BlueprintClipboard { if (entity instanceof Horse horse) { bpe.setStyle(horse.getStyle()); } + + mmh.filter(mm -> mm.isMythicMob(entity)).map(mm -> mm.getMythicMob(entity)) + .ifPresent(mmr -> bpe.setMythicMobsRecord(mmr)); + bpEnts.add(bpe); } return bpEnts; 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 a06c11932..bc8f72ae3 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java @@ -24,6 +24,19 @@ import com.google.gson.annotations.Expose; */ public class BlueprintEntity { + public record MythicMobRecord(String type, String displayName, double level, float power, String stance) { + }; + + // GSON can serialize records, but the record class needs to be know in advance. So this breaks out the record entries + @Expose + String MMtype; + @Expose + Double MMLevel; + @Expose + String MMStance; + @Expose + Float MMpower; + @Expose private DyeColor color; @Expose @@ -50,7 +63,6 @@ public class BlueprintEntity { private Integer experience; @Expose private Villager.Type villagerType; - /** * @since 1.8.0 @@ -85,7 +97,6 @@ public class BlueprintEntity { if (style != null && e instanceof Horse horse) { horse.setStyle(style); } - } /** @@ -270,5 +281,24 @@ public class BlueprintEntity { public void setDomestication(Integer domestication) { this.domestication = domestication; } + + /** + * @return the mythicMobsRecord + */ + public MythicMobRecord getMythicMobsRecord() { + return new MythicMobRecord(this.MMtype, this.getCustomName(), this.MMLevel, this.MMpower, this.MMStance); + } + + /** + * @param mythicMobsRecord the mythicMobsRecord to set + * @since 2.1.0 + */ + public void setMythicMobsRecord(MythicMobRecord mmr) { + this.setCustomName(mmr.displayName()); + this.MMtype = mmr.type(); + this.MMLevel = mmr.level(); + this.MMStance = mmr.stance(); + this.MMpower = mmr.power(); + } } diff --git a/src/main/java/world/bentobox/bentobox/hooks/MythicMobsHook.java b/src/main/java/world/bentobox/bentobox/hooks/MythicMobsHook.java new file mode 100644 index 000000000..5db726a27 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/hooks/MythicMobsHook.java @@ -0,0 +1,70 @@ +package world.bentobox.bentobox.hooks; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Entity; + +import io.lumine.mythic.bukkit.BukkitAdapter; +import io.lumine.mythic.bukkit.MythicBukkit; +import io.lumine.mythic.core.mobs.ActiveMob; +import world.bentobox.bentobox.api.hooks.Hook; +import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity.MythicMobRecord; + +/** + * Provides implementation and interfacing to interact with MythicMobs. + * + * @author tastybento + * @since 2.2.0 + */ +public class MythicMobsHook extends Hook { + + public MythicMobsHook() { + super("MythicMobs", Material.CREEPER_HEAD); + } + + public boolean isMythicMob(Entity bukkitEntity) { + return MythicBukkit.inst().getMobManager().isMythicMob(bukkitEntity); + } + + public MythicMobRecord getMythicMob(Entity bukkitEntity) { + ActiveMob mm = MythicBukkit.inst().getMobManager().getActiveMob(bukkitEntity.getUniqueId()).orElse(null); + if (mm != null) { + return new MythicMobRecord(mm.getMobType(), mm.getDisplayName(), mm.getLevel(), + mm.getPower(), + mm.getStance()); + } + return null; + } + + + @Override + public boolean hook() { + return true; // The hook process shouldn't fail + } + + @Override + public String getFailureCause() { + return null; // The hook process shouldn't fail + } + + /** + * Spawn a MythicMob + * @param mmr MythicMobRecord + * @param spawnLocation location + * @return true if spawn is successful + */ + public boolean spawnMythicMob(MythicMobRecord mmr, Location spawnLocation) { + return MythicBukkit.inst().getMobManager().getMythicMob(mmr.type()).map(mob -> { + // A delay is required before spawning, I assume because the blocks are pasted using NMS + Bukkit.getScheduler().runTaskLater(getPlugin(), () -> { + // spawns mob + ActiveMob activeMob = mob.spawn(BukkitAdapter.adapt(spawnLocation), mmr.level()); + activeMob.setDisplayName(mmr.displayName()); + activeMob.setPower(mmr.power()); + activeMob.setStance(mmr.stance()); + }, 40L); + return true; + }).orElse(false); + } +} diff --git a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java index e616e35a3..76fb68be9 100644 --- a/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java +++ b/src/main/java/world/bentobox/bentobox/util/DefaultPasteUtil.java @@ -33,6 +33,7 @@ import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock; import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner; import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.hooks.MythicMobsHook; import world.bentobox.bentobox.nms.PasteHandler; /** @@ -172,29 +173,46 @@ public class DefaultPasteUtil { public static CompletableFuture setEntity(Island island, Location location, List list) { World world = location.getWorld(); assert world != null; - return Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null).forEach(k -> { - LivingEntity e = (LivingEntity) location.getWorld().spawnEntity(location, k.getType()); - if (k.getCustomName() != null) { - String customName = k.getCustomName(); + return Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null) + .forEach(k -> spawnBlueprintEntity(k, location, island))); + } - 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); - } + /** + * Spawn an entity + * @param k the blueprint entity definition + * @param location location + * @param island island + * @return true if Bukkit entity spawned, false if MythicMob entity spawned + */ + static boolean spawnBlueprintEntity(BlueprintEntity k, Location location, Island island) { + if (k.getMythicMobsRecord() != null && plugin.getHooks().getHook("MythicMobs") + .filter(mmh -> mmh instanceof MythicMobsHook) + .map(mmh -> ((MythicMobsHook) mmh).spawnMythicMob(k.getMythicMobsRecord(), location)) + .orElse(false)) { + // MythicMob has spawned. + return false; + } + LivingEntity e = (LivingEntity) location.getWorld().spawnEntity(location, k.getType()); + if (k.getCustomName() != null) { + String customName = k.getCustomName(); + + if (island != null) { + // Parse any placeholders in the entity's name, if the owner's connected (he should) + Optional owner = Optional.ofNullable(island.getOwner()).map(User::getInstance) + .map(User::getPlayer); + if (owner.isPresent()) { + // Parse for the player's name first (in case placeholders might need it) + customName = customName.replace(TextVariables.NAME, owner.get().getName()); + // Now parse the placeholders + customName = plugin.getPlaceholdersManager().replacePlaceholders(owner.get(), customName); } - - // Actually set the custom name - e.setCustomName(customName); } - k.configureEntity(e); - })); + + // Actually set the custom name + e.setCustomName(customName); + } + k.configureEntity(e); + return true; } /** diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index ac15aa754..0ca09ce7d 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.18" +api-version: "1.20" authors: [tastybento, Poslovitch] contributors: ["The BentoBoxWorld Community"] @@ -17,15 +17,13 @@ softdepend: - Vault - PlaceholderAPI - dynmap - - WorldBorderAPI - BsbMongo - - WorldGeneratorApi - AdvancedChests - LangUtils - WildStacker - LuckPerms - - HolographicDisplays - EconomyPlus + - MythicMobs libraries: - mysql:mysql-connector-java:${mysql.version} diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommandTest.java index 428b702b7..97e7f2d53 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommandTest.java @@ -40,6 +40,7 @@ import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.blueprints.Blueprint; import world.bentobox.bentobox.managers.BlueprintsManager; import world.bentobox.bentobox.managers.CommandsManager; +import world.bentobox.bentobox.managers.HooksManager; import world.bentobox.bentobox.managers.LocalesManager; /** @@ -73,6 +74,11 @@ public class AdminBlueprintLoadCommandTest { // Set up plugin Whitebox.setInternalState(BentoBox.class, "instance", plugin); + // Hooks + HooksManager hooksManager = mock(HooksManager.class); + when(hooksManager.getHook(anyString())).thenReturn(Optional.empty()); + when(plugin.getHooks()).thenReturn(hooksManager); + // Blueprints Manager when(plugin.getBlueprintsManager()).thenReturn(bm); diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommandTest.java index af71e869f..1ab109bc5 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommandTest.java @@ -18,6 +18,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import org.bukkit.Bukkit; @@ -41,6 +42,7 @@ import world.bentobox.bentobox.blueprints.Blueprint; import world.bentobox.bentobox.blueprints.BlueprintClipboard; import world.bentobox.bentobox.managers.BlueprintsManager; import world.bentobox.bentobox.managers.CommandsManager; +import world.bentobox.bentobox.managers.HooksManager; import world.bentobox.bentobox.managers.LocalesManager; /** @@ -58,7 +60,7 @@ public class AdminBlueprintSaveCommandTest { private GameModeAddon addon; @Mock private User user; - private BlueprintClipboard clip = new BlueprintClipboard(); + private BlueprintClipboard clip; private UUID uuid = UUID.randomUUID(); private File blueprintsFolder; @Mock @@ -72,6 +74,12 @@ public class AdminBlueprintSaveCommandTest { // Set up plugin BentoBox plugin = mock(BentoBox.class); Whitebox.setInternalState(BentoBox.class, "instance", plugin); + // Hooks + HooksManager hooksManager = mock(HooksManager.class); + when(hooksManager.getHook(anyString())).thenReturn(Optional.empty()); + when(plugin.getHooks()).thenReturn(hooksManager); + + clip = new BlueprintClipboard(); // Blueprints Manager when(plugin.getBlueprintsManager()).thenReturn(bm); @@ -109,7 +117,6 @@ public class AdminBlueprintSaveCommandTest { PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); - absc = new AdminBlueprintSaveCommand(ac); } diff --git a/src/test/java/world/bentobox/bentobox/blueprints/BlueprintClipboardTest.java b/src/test/java/world/bentobox/bentobox/blueprints/BlueprintClipboardTest.java index dbe08cd9c..861b0f8be 100644 --- a/src/test/java/world/bentobox/bentobox/blueprints/BlueprintClipboardTest.java +++ b/src/test/java/world/bentobox/bentobox/blueprints/BlueprintClipboardTest.java @@ -4,11 +4,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.List; +import java.util.Optional; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -29,6 +32,7 @@ import org.powermock.reflect.Whitebox; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.managers.HooksManager; /** * @author tastybento @@ -56,6 +60,10 @@ public class BlueprintClipboardTest { public void setUp() throws Exception { // Set up plugin Whitebox.setInternalState(BentoBox.class, "instance", plugin); + // Hooks + HooksManager hooksManager = mock(HooksManager.class); + when(hooksManager.getHook(anyString())).thenReturn(Optional.empty()); + when(plugin.getHooks()).thenReturn(hooksManager); // User when(user.getTranslation(Mockito.anyString())).thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); diff --git a/src/test/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntityTest.java b/src/test/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntityTest.java index bb5dec831..ed2bb7e9d 100644 --- a/src/test/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntityTest.java +++ b/src/test/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntityTest.java @@ -1,5 +1,6 @@ package world.bentobox.bentobox.blueprints.dataobjects; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; import java.util.HashMap; @@ -26,6 +27,8 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.powermock.modules.junit4.PowerMockRunner; +import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity.MythicMobRecord; + /** * @author tastybento * @@ -84,10 +87,10 @@ public class BlueprintEntityTest { blueprint.configureEntity(villager); - Assert.assertEquals(Profession.LIBRARIAN, villager.getProfession()); - Assert.assertEquals(100, villager.getVillagerExperience()); - Assert.assertEquals(2, villager.getVillagerLevel()); - Assert.assertEquals(Villager.Type.PLAINS, villager.getVillagerType()); + assertEquals(Profession.LIBRARIAN, villager.getProfession()); + assertEquals(100, villager.getVillagerExperience()); + assertEquals(2, villager.getVillagerLevel()); + assertEquals(Villager.Type.PLAINS, villager.getVillagerType()); } @Test @@ -99,7 +102,7 @@ public class BlueprintEntityTest { blueprint.configureEntity(sheep); - Assert.assertEquals(DyeColor.BLUE, sheep.getColor()); + assertEquals(DyeColor.BLUE, sheep.getColor()); } @Test @@ -147,7 +150,7 @@ public class BlueprintEntityTest { blueprint.configureEntity(horse); - Assert.assertEquals(50, horse.getDomestication()); + assertEquals(50, horse.getDomestication()); } @Test @@ -159,7 +162,7 @@ public class BlueprintEntityTest { blueprint.configureEntity(horse); - Assert.assertEquals(Style.WHITE_DOTS, horse.getStyle()); + assertEquals(Style.WHITE_DOTS, horse.getStyle()); } @Test @@ -167,13 +170,13 @@ public class BlueprintEntityTest { BlueprintEntity blueprint = new BlueprintEntity(); blueprint.setColor(DyeColor.RED); - Assert.assertEquals(DyeColor.RED, blueprint.getColor()); + assertEquals(DyeColor.RED, blueprint.getColor()); blueprint.setType(EntityType.CREEPER); - Assert.assertEquals(EntityType.CREEPER, blueprint.getType()); + assertEquals(EntityType.CREEPER, blueprint.getType()); blueprint.setCustomName("My Entity"); - Assert.assertEquals("My Entity", blueprint.getCustomName()); + assertEquals("My Entity", blueprint.getCustomName()); blueprint.setTamed(true); Assert.assertTrue(blueprint.getTamed()); @@ -185,27 +188,35 @@ public class BlueprintEntityTest { Assert.assertFalse(blueprint.getAdult()); blueprint.setDomestication(75); - Assert.assertEquals(75, blueprint.getDomestication().intValue()); + assertEquals(75, blueprint.getDomestication().intValue()); Map inventory = new HashMap<>(); inventory.put(1, new ItemStack(Material.DIAMOND)); blueprint.setInventory(inventory); - Assert.assertEquals(inventory, blueprint.getInventory()); + assertEquals(inventory, blueprint.getInventory()); blueprint.setStyle(Style.WHITE); - Assert.assertEquals(Style.WHITE, blueprint.getStyle()); + assertEquals(Style.WHITE, blueprint.getStyle()); blueprint.setLevel(5); - Assert.assertEquals(5, blueprint.getLevel().intValue()); + assertEquals(5, blueprint.getLevel().intValue()); blueprint.setProfession(Profession.FARMER); - Assert.assertEquals(Profession.FARMER, blueprint.getProfession()); + assertEquals(Profession.FARMER, blueprint.getProfession()); blueprint.setExperience(500); - Assert.assertEquals(500, blueprint.getExperience().intValue()); + assertEquals(500, blueprint.getExperience().intValue()); blueprint.setVillagerType(Villager.Type.TAIGA); - Assert.assertEquals(Villager.Type.TAIGA, blueprint.getVillagerType()); + assertEquals(Villager.Type.TAIGA, blueprint.getVillagerType()); + } + + @Test + public void testMythicMobs() { + BlueprintEntity blueprint = new BlueprintEntity(); + MythicMobRecord mmr = new MythicMobRecord("string", "string2", 10D, 1F, "string3"); + blueprint.setMythicMobsRecord(mmr); + assertEquals(mmr, blueprint.getMythicMobsRecord()); } } diff --git a/src/test/java/world/bentobox/bentobox/hooks/MythicMobsHookTest.java b/src/test/java/world/bentobox/bentobox/hooks/MythicMobsHookTest.java new file mode 100644 index 000000000..360befc6f --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/hooks/MythicMobsHookTest.java @@ -0,0 +1,165 @@ +package world.bentobox.bentobox.hooks; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import io.lumine.mythic.api.mobs.MythicMob; +import io.lumine.mythic.bukkit.MythicBukkit; +import io.lumine.mythic.core.mobs.ActiveMob; +import io.lumine.mythic.core.mobs.MobExecutor; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity.MythicMobRecord; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ BentoBox.class, Bukkit.class, MythicBukkit.class }) +public class MythicMobsHookTest { + + @Mock + private BentoBox plugin; + @Mock + private PluginManager pim; + @Mock + private Plugin mythicMobs; + @Mock + private Location location; + @Mock + private World world; + // DUT + MythicMobsHook hook; + @Mock + private MythicBukkit mythicBukkit; + @Mock + private MobExecutor mm; + @Mock + private MythicMob mythicMob; + @Mock + private ActiveMob activeMob; + @Mock + private Entity entity; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Set up plugin + plugin = mock(BentoBox.class); + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + // Bukkit + PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); + when(Bukkit.getPluginManager()).thenReturn(pim); + when(pim.getPlugin("MythicMobs")).thenReturn(mythicMobs); + // Location + when(world.getName()).thenReturn("bskyblock"); + when(location.getWorld()).thenReturn(world); + // Entity + when(entity.getUniqueId()).thenReturn(UUID.randomUUID()); + // MythicMobs + PowerMockito.mockStatic(MythicBukkit.class, Mockito.RETURNS_MOCKS); + when(MythicBukkit.inst()).thenReturn(mythicBukkit); + when(mythicBukkit.getMobManager()).thenReturn(mm); + when(mm.getMythicMob(anyString())).thenReturn(Optional.of(mythicMob)); + when(activeMob.getDisplayName()).thenReturn("Minion"); + when(activeMob.getMobType()).thenReturn("GIANT"); + when(activeMob.getStance()).thenReturn("default"); + when(activeMob.getLevel()).thenReturn(2.5D); + when(activeMob.getPower()).thenReturn(33.2F); + when(mm.getActiveMob(any())).thenReturn(Optional.of(activeMob)); + + hook = new MythicMobsHook(); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#hook()}. + */ + @Test + public void testHook() { + assertTrue(hook.hook()); + } + + /** + * Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#getFailureCause()}. + */ + @Test + public void testGetFailureCause() { + assertNull(hook.getFailureCause()); + } + + /** + * Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#MythicMobsHook()}. + */ + @Test + public void testMythicMobsHook() { + assertNotNull(hook); + assertEquals(Material.CREEPER_HEAD, hook.getIcon()); + + } + + /** + * Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#isMythicMob(org.bukkit.entity.Entity)}. + */ + @Test + public void testIsMythicMob() { + assertFalse(hook.isMythicMob(entity)); + } + + /** + * Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#getMythicMob(org.bukkit.entity.Entity)}. + */ + @Test + public void testGetMythicMob() { + MythicMobRecord mmr = hook.getMythicMob(entity); + assertEquals("GIANT", mmr.type()); + assertEquals("Minion", mmr.displayName()); + assertEquals("default", mmr.stance()); + assertEquals(2.5D, mmr.level(), 0D); + assertEquals(33.2F, mmr.power(), 0F); + } + + /** + * Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#spawnMythicMob(world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity.MythicMobRecord, org.bukkit.Location)}. + */ + @Test + public void testSpawnMythicMob() { + MythicMobRecord mmr = hook.getMythicMob(entity); + assertTrue(hook.spawnMythicMob(mmr, location)); + verify(mm).getMythicMob("GIANT"); + } + +} diff --git a/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java index 30e24cb16..53804ab00 100644 --- a/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/BlueprintClipboardManagerTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -16,6 +17,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Comparator; +import java.util.Optional; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -33,6 +35,7 @@ import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; @@ -128,13 +131,20 @@ public class BlueprintClipboardManagerTest { blueprintFolder = new File("blueprints"); // Clear any residual files tearDown(); + // Set up plugin + BentoBox plugin = mock(BentoBox.class); + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + // Hooks + HooksManager hooksManager = mock(HooksManager.class); + when(hooksManager.getHook(anyString())).thenReturn(Optional.empty()); + when(plugin.getHooks()).thenReturn(hooksManager); + PowerMockito.mockStatic(Bukkit.class); BlockData blockData = mock(BlockData.class); when(Bukkit.createBlockData(any(Material.class))).thenReturn(blockData); when(blockData.getAsString()).thenReturn("test123"); when(server.getBukkitVersion()).thenReturn("version"); when(Bukkit.getServer()).thenReturn(server); - } /** diff --git a/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java b/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java index f383c7f7d..75cc62b8b 100644 --- a/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java +++ b/src/test/java/world/bentobox/bentobox/util/DefaultPasteUtilTest.java @@ -1,9 +1,12 @@ package world.bentobox.bentobox.util; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -25,10 +28,13 @@ import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.WallSign; import org.bukkit.block.sign.Side; import org.bukkit.block.sign.SignSide; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -43,7 +49,11 @@ import world.bentobox.bentobox.api.addons.GameModeAddon; 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.BlueprintEntity; +import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity.MythicMobRecord; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.hooks.MythicMobsHook; +import world.bentobox.bentobox.managers.HooksManager; import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.LocalesManager; import world.bentobox.bentobox.managers.PlayersManager; @@ -79,10 +89,20 @@ public class DefaultPasteUtilTest { @Mock(extraInterfaces = {org.bukkit.block.Sign.class}) BlockState sign; + @Mock + private PlayersManager pm; + @Mock + private MythicMobsHook mythicMobsHook; + @Mock + private BlueprintEntity blueprintEntity; + @Mock + private Location location; + @Mock + private LivingEntity livingEntity; @Mock private World world; @Mock - private PlayersManager pm; + private HooksManager hooksManager; /** @@ -110,6 +130,11 @@ public class DefaultPasteUtilTest { when(plugin.getLocalesManager()).thenReturn(localesManager); when(localesManager.getOrDefault(any(), anyString(), anyString())).thenReturn("translated"); + when(location.getWorld()).thenReturn(world); + // Hooks + when(hooksManager.getHook("MythicMobs")).thenReturn(Optional.of(mythicMobsHook)); + when(plugin.getHooks()).thenReturn(hooksManager); + when(plugin.getPlayers()).thenReturn(pm); } @@ -188,4 +213,32 @@ public class DefaultPasteUtilTest { List capturedLines = lineCaptor.getAllValues(); Assert.assertEquals(linesTranslated, capturedLines); } + + @Ignore + @Test + public void testSpawnBlueprintEntity_WithMythicMobs() { + // Set up conditions to satisfy the mythic mobs spawning logic + MythicMobRecord mmr = new MythicMobRecord("string", "string2", 10D, 1F, "string3"); + when(blueprintEntity.getMythicMobsRecord()).thenReturn(mmr); + when(mythicMobsHook.spawnMythicMob(mmr, location)).thenReturn(true); + // This test works fine if there is a System.out.println() in the code. I assume some optimization is being done in compilation + + assertFalse(DefaultPasteUtil.spawnBlueprintEntity(blueprintEntity, location, island)); + + // Verify the mythic mob was spawned, and the method returned early + verify(mythicMobsHook).spawnMythicMob(mmr, location); + verify(world, never()).spawnEntity(any(Location.class), any(EntityType.class)); + } + + @Test + public void testSpawnBlueprintEntity_WithoutMythicMobs() { + // Set up conditions where MythicMobs should not be spawned + when(hooksManager.getHook("MythicMobs")).thenReturn(Optional.empty()); + + assertTrue(DefaultPasteUtil.spawnBlueprintEntity(blueprintEntity, location, island)); + + // Verify a regular entity was spawned instead + verify(world).spawnEntity(location, blueprintEntity.getType()); + } + } From 7f532b1257de58789152b56691f3c0b5647b1be9 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 10 Mar 2024 11:21:03 -0700 Subject: [PATCH 08/24] Resolve JavaDoc issues --- .../bentobox/blueprints/dataobjects/BlueprintEntity.java | 2 +- .../listeners/flags/protection/PhysicalInteractionListener.java | 2 +- .../listeners/flags/protection/PlaceBlocksListener.java | 1 + src/main/java/world/bentobox/bentobox/lists/Flags.java | 2 +- .../java/world/bentobox/bentobox/managers/IslandsManager.java | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) 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 bc8f72ae3..ec411cb91 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java @@ -290,7 +290,7 @@ public class BlueprintEntity { } /** - * @param mythicMobsRecord the mythicMobsRecord to set + * @param mmr the mythicMobsRecord to set * @since 2.1.0 */ public void setMythicMobsRecord(MythicMobRecord mmr) { 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 1612909e8..bef997cd8 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 @@ -15,7 +15,7 @@ import world.bentobox.bentobox.lists.Flags; /** - * Listener for {@link Flags#CROP_TRAMPLE, Flags#PRESSURE_PLATE, Flags#TURTLE_EGGS, Flags#BUTTON} + * Listener for {@link Flags#CROP_TRAMPLE}, {@link Flags#PRESSURE_PLATE}, {@link Flags#TURTLE_EGGS}, {@link Flags#BUTTON} * @author tastybento * */ 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 df189f03f..9b621a7e2 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 @@ -20,6 +20,7 @@ import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; /** + * Provides protection for placing blocks. * @author tastybento */ public class PlaceBlocksListener extends FlagListener diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index b5c7d59c5..9fc685b24 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -674,7 +674,7 @@ public final class Flags { /** * Crop Planting * Controls who gets to plant crops on tilled soil. - * Listener is {@link PlaceBlockListener} + * Listener is {@link world.bentobox.bentobox.listeners.flags.protection.PlaceBlocksListener} * @since 1.23.0 */ public static final Flag CROP_PLANTING = new Flag.Builder("CROP_PLANTING", Material.PUMPKIN_SEEDS).mode(Flag.Mode.BASIC).type(Type.PROTECTION).build(); diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index fff6cd23a..af7dea9cd 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -341,7 +341,7 @@ public class IslandsManager { * Gets all the islands for this player in this world that this player owns. * * @param world world to check - * @param uniqueId user's UUID + * @param user user * @return List of islands or empty list if none found for user * @since 2.1.0 */ From 4170616e4795bfbd51ef6f68da3b69a17a66c518 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 10 Mar 2024 11:29:51 -0700 Subject: [PATCH 09/24] Fixes bug where non-members could be made island owners. --- .../bentobox/api/commands/island/team/IslandTeamCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java index ee8d33f59..123fb0861 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java @@ -520,7 +520,8 @@ public class IslandTeamCommand extends CompositeCommand { case "SETOWNER" -> { // Make the player the leader of the island if (clickingUser.hasPermission(this.setOwnerCommand.getPermission()) && !target.equals(clickingUser) - && clickingUser.getUniqueId().equals(island.getOwner())) { + && clickingUser.getUniqueId().equals(island.getOwner()) + && island.getRank(target) >= RanksManager.MEMBER_RANK) { getPlugin().log("Set Owner: " + clickingUser.getName() + " trying to make " + target.getName() + " owner of island at " + island.getCenter()); clickingUser.closeInventory(); From dc42f51168bd8dc7787d843eea85a9d6c9413b08 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 10 Mar 2024 11:46:44 -0700 Subject: [PATCH 10/24] Uses path normalization to prevent directory traversal attacks. --- .../bentobox/managers/BlueprintClipboardManager.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java index 8afb589fc..4a5e66c33 100644 --- a/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/BlueprintClipboardManager.java @@ -214,8 +214,11 @@ public class BlueprintClipboardManager { } private void unzipFiles(final ZipInputStream zipInputStream, final Path unzipFilePath) throws IOException { - if (!unzipFilePath.toFile().getCanonicalPath().startsWith(blueprintFolder.getCanonicalPath())) { - throw new IOException("Entry is outside of the target directory"); + // Prevent directory traversal attacks by normalizing the path + if (!unzipFilePath.startsWith(blueprintFolder.getCanonicalFile().toPath().normalize())) { + throw new IOException( + "Blueprint file is trying to write outside of the target directory! Blocked attempt to write to " + + unzipFilePath.toString()); } try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(unzipFilePath.toFile().getCanonicalPath()))) { byte[] bytesIn = new byte[1024]; From 253e5d7101ecf6fe61bb7e38c0d8d7af2fc4c450 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 10 Mar 2024 22:36:42 -0700 Subject: [PATCH 11/24] Use method references. --- .../bentobox/bentobox/blueprints/BlueprintClipboard.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java index 048bfeec4..7a38e76f1 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java @@ -78,14 +78,14 @@ public class BlueprintClipboard { public BlueprintClipboard(@NonNull Blueprint blueprint) { this.blueprint = blueprint; // MythicMobs - mmh = plugin.getHooks().getHook("MythicMobs").filter(hook -> hook instanceof MythicMobsHook) - .map(h -> (MythicMobsHook) h); + mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance) + .map(MythicMobsHook.class::cast); } public BlueprintClipboard() { // MythicMobs - mmh = plugin.getHooks().getHook("MythicMobs").filter(hook -> hook instanceof MythicMobsHook) - .map(h -> (MythicMobsHook) h); + mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance) + .map(MythicMobsHook.class::cast); } /** From eef3dcbc468491ffe2b4dfaa520f02efbb22963a Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 13 Mar 2024 17:58:28 -0700 Subject: [PATCH 12/24] Allow the maxhomes to apply per island. --- .../api/commands/island/IslandSethomeCommand.java | 2 +- .../commands/island/IslandSethomeCommandTest.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommand.java index 9ccde2194..0db39b132 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommand.java @@ -53,7 +53,7 @@ public class IslandSethomeCommand extends ConfirmableCommand { // Check number of homes - int maxHomes = getIslands().getIslands(getWorld(), user).stream().mapToInt(getIslands()::getMaxHomes).sum(); + int maxHomes = getIslands().getMaxHomes(island); if (getIslands().getNumberOfHomesIfAdded(island, String.join(" ", args)) > maxHomes) { user.sendMessage("commands.island.sethome.too-many-homes", TextVariables.NUMBER, String.valueOf(maxHomes)); user.sendMessage("commands.island.sethome.homes-are"); diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommandTest.java index fccdd060b..c80f05563 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSethomeCommandTest.java @@ -37,6 +37,7 @@ import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.configuration.WorldSettings; +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.managers.CommandsManager; @@ -195,6 +196,20 @@ public class IslandSethomeCommandTest { verify(user).sendMessage("commands.island.sethome.must-be-on-your-island"); } + /** + * Test method for + * {@link world.bentobox.bentobox.api.commands.island.IslandSethomeCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testCanExecuteTooManyHomes() { + when(im.getMaxHomes(island)).thenReturn(10); + when(im.getNumberOfHomesIfAdded(eq(island), anyString())).thenReturn(11); + IslandSethomeCommand isc = new IslandSethomeCommand(ic); + assertFalse(isc.canExecute(user, "island", Collections.emptyList())); + verify(user).sendMessage("commands.island.sethome.too-many-homes", TextVariables.NUMBER, "10"); + verify(user).sendMessage("commands.island.sethome.homes-are"); + } + /** * Test method for * {@link world.bentobox.bentobox.api.commands.island.IslandSethomeCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. From 6db04f872b7f5e9939f5df2f21bdf70bb8442302 Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 14 Mar 2024 18:16:09 -0700 Subject: [PATCH 13/24] Fix promote and demote #2322 --- .../island/team/IslandTeamPromoteCommand.java | 13 +++++++++++-- src/main/resources/locales/en-US.yml | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommand.java index eab48723a..e35e8aeea 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommand.java @@ -15,6 +15,9 @@ import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Util; +/** + * Handle promotion and demotion + */ public class IslandTeamPromoteCommand extends CompositeCommand { private User target; @@ -45,7 +48,7 @@ public class IslandTeamPromoteCommand extends CompositeCommand { showHelp(this, user); return false; } - + // Check if the user has a team if (!getIslands().inTeam(getWorld(), user.getUniqueId())) { user.sendMessage("general.errors.no-team"); return false; @@ -65,6 +68,11 @@ public class IslandTeamPromoteCommand extends CompositeCommand { user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); return false; } + // Check that target is a member of this island + if (!island.getMemberSet().contains(target.getUniqueId())) { + user.sendMessage("commands.island.team.promote.errors.must-be-member"); + return false; + } // Check if the user is not trying to promote/ demote himself if (target.equals(user)) { if (this.getLabel().equals("promote")) { @@ -100,7 +108,8 @@ public class IslandTeamPromoteCommand extends CompositeCommand { if (this.getLabel().equals("promote")) { int nextRank = RanksManager.getInstance().getRankUpValue(currentRank); // Stop short of owner - if (nextRank != RanksManager.OWNER_RANK && nextRank > currentRank) { + if (nextRank < RanksManager.OWNER_RANK && currentRank >= RanksManager.MEMBER_RANK + && nextRank > currentRank) { island.setRank(target, nextRank); String rankName = user.getTranslation(RanksManager.getInstance().getRank(nextRank)); user.sendMessage("commands.island.team.promote.success", TextVariables.NAME, target.getName(), TextVariables.RANK, rankName, TextVariables.DISPLAY_NAME, target.getDisplayName()); diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 2bbbd78bb..61af1f1a1 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -786,6 +786,7 @@ commands: errors: cant-demote-yourself: '&c You can''t demote yourself!' cant-demote: '&c You can''t demote higher ranks!' + must-be-member: '&c Player must be an island member!' failure: '&c Player cannot be demoted any further!' success: '&a Demoted [name] to [rank]' promote: @@ -794,6 +795,7 @@ commands: errors: cant-promote-yourself: '&c You can''t promote yourself!' cant-promote: '&c You can''t promote above your rank!' + must-be-member: '&c Player must be an island member!' failure: '&c Player cannot be promoted any further!' success: '&a Promoted [name] to [rank]' setowner: From 977c82015ba3063f4550ac0caf74e5bff9da59a2 Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 14 Mar 2024 20:59:20 -0700 Subject: [PATCH 14/24] Rewrite. Tested with players. --- .../island/team/InviteNamePrompt.java | 53 -- .../island/team/IslandTeamCommand.java | 590 ++---------------- .../commands/island/team/IslandTeamGUI.java | 536 ++++++++++++++++ .../island/team/IslandTeamInviteCommand.java | 225 +------ .../island/team/IslandTeamInviteGUI.java | 279 +++++++++ 5 files changed, 869 insertions(+), 814 deletions(-) delete mode 100644 src/main/java/world/bentobox/bentobox/api/commands/island/team/InviteNamePrompt.java create mode 100644 src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java create mode 100644 src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/InviteNamePrompt.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/InviteNamePrompt.java deleted file mode 100644 index d9fde3cda..000000000 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/InviteNamePrompt.java +++ /dev/null @@ -1,53 +0,0 @@ -package world.bentobox.bentobox.api.commands.island.team; - -import java.util.List; - -import org.bukkit.Bukkit; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; -import org.bukkit.conversations.StringPrompt; -import org.eclipse.jdt.annotation.NonNull; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.user.User; - -/** - * Invites a player by search - * @author tastybento - * - */ -public class InviteNamePrompt extends StringPrompt { - - @NonNull - private final User user; - @NonNull - private final IslandTeamInviteCommand itic; - - public InviteNamePrompt(@NonNull User user, IslandTeamInviteCommand islandTeamInviteCommand) { - this.user = user; - this.itic = islandTeamInviteCommand; - } - - @Override - @NonNull - public String getPromptText(@NonNull ConversationContext context) { - return user.getTranslation("commands.island.team.invite.gui.enter-name"); - } - - @Override - public Prompt acceptInput(@NonNull ConversationContext context, String input) { - // TODO remove this and pass the options back to the GUI - if (itic.canExecute(user, itic.getLabel(), List.of(input))) { - if (itic.execute(user, itic.getLabel(), List.of(input))) { - return Prompt.END_OF_CONVERSATION; - } - } - // Set the search item to what was entered - itic.setSearchName(input); - // Return to the GUI but give a second for the error to show - // TODO: return the failed input and display the options in the GUI. - Bukkit.getScheduler().runTaskLater(BentoBox.getInstance(), () -> itic.build(user), 20L); - return Prompt.END_OF_CONVERSATION; - } - -} diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java index 123fb0861..fd1d06e82 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java @@ -1,24 +1,12 @@ package world.bentobox.bentobox.api.commands.island.team; import java.io.File; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.UUID; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.Sound; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; @@ -26,40 +14,19 @@ import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.events.IslandBaseEvent; import world.bentobox.bentobox.api.events.team.TeamEvent; import world.bentobox.bentobox.api.localization.TextVariables; -import world.bentobox.bentobox.api.panels.Panel; -import world.bentobox.bentobox.api.panels.PanelItem; -import world.bentobox.bentobox.api.panels.TemplatedPanel; -import world.bentobox.bentobox.api.panels.TemplatedPanel.ItemSlot; -import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; -import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder; -import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; -import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords; import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.managers.RanksManager; -import world.bentobox.bentobox.util.Util; public class IslandTeamCommand extends CompositeCommand { - /** - * List of ranks that we will loop through in order - */ - private static final List RANKS = List.of(RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK, - RanksManager.MEMBER_RANK, RanksManager.TRUSTED_RANK, RanksManager.COOP_RANK); - /** * Invited list. Key is the invited party, value is the invite. * @since 1.8.0 */ private final Map inviteMap; - private User user; - - private Island island; - - private int rank = RanksManager.OWNER_RANK; - private IslandTeamKickCommand kickCommand; private IslandTeamLeaveCommand leaveCommand; @@ -120,13 +87,12 @@ public class IslandTeamCommand extends CompositeCommand { @Override public boolean canExecute(User user, String label, List args) { - this.user = user; // Player issuing the command must have an island - island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()); + Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()); if (island == null) { if (isInvited(user.getUniqueId())) { // Player has an invite, so show the invite - build(); + new IslandTeamGUI(getPlugin(), this, user, island).build(); return true; } user.sendMessage("general.errors.no-island"); @@ -155,511 +121,11 @@ public class IslandTeamCommand extends CompositeCommand { @Override public boolean execute(User user, String label, List args) { // Show the panel - build(); + new IslandTeamGUI(getPlugin(), this, user, getIslands().getPrimaryIsland(getWorld(), user.getUniqueId())) + .build(); return true; } - /** - * This method builds this GUI. - */ - void build() { - // Start building panel. - TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); - panelBuilder.user(user); - panelBuilder.world(user.getWorld()); - - panelBuilder.template("team_panel", new File(getPlugin().getDataFolder(), "panels")); - - panelBuilder.parameters("[name]", user.getName(), "[display_name]", user.getDisplayName()); - - panelBuilder.registerTypeBuilder("STATUS", this::createStatusButton); - panelBuilder.registerTypeBuilder("MEMBER", this::createMemberButton); - panelBuilder.registerTypeBuilder("INVITED", this::createInvitedButton); - panelBuilder.registerTypeBuilder("RANK", this::createRankButton); - panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton); - border = panelBuilder.getPanelTemplate().border(); - background = panelBuilder.getPanelTemplate().background(); - // Register unknown type builder. - panelBuilder.build(); - } - - private PanelItem createInviteButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { - if (island == null || !user.hasPermission(this.inviteCommand.getPermission()) - || island.getRank(user) < island.getRankCommand(this.getLabel() + " invite")) { - return this.getBlankBorder(); - } - PanelItemBuilder builder = new PanelItemBuilder(); - builder.icon(Material.PLAYER_HEAD); - builder.name(user.getTranslation("commands.island.team.gui.buttons.invite.name")); - builder.description(user.getTranslation("commands.island.team.gui.buttons.invite.description")); - builder.clickHandler((panel, user, clickType, clickSlot) -> { - if (!template.actions().stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { - // If the click type is not in the template, don't do anything - return true; - } - if (clickType.equals(ClickType.LEFT)) { - user.closeInventory(); - this.inviteCommand.build(user); - } - return true; - }); - return builder.build(); - } - - private PanelItem createRankButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { - // If there is no island, the do not show this icon - if (island == null) { - return this.getBlankBorder(); - } - PanelItemBuilder builder = new PanelItemBuilder(); - builder.name(user.getTranslation("commands.island.team.gui.buttons.rank-filter.name")); - builder.icon(Material.AMETHYST_SHARD); - // Create description - RanksManager.getInstance().getRanks().forEach((reference, score) -> { - if (rank == RanksManager.OWNER_RANK && score > RanksManager.VISITOR_RANK - && score <= RanksManager.OWNER_RANK) { - builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank") - + user.getTranslation(reference)); - } else if (score > RanksManager.VISITOR_RANK && score < rank) { - builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank") - + user.getTranslation(reference)); - } else if (score <= RanksManager.OWNER_RANK && score > rank) { - builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank") - + user.getTranslation(reference)); - } else if (score == rank) { - builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank") - + user.getTranslation(reference)); - } - }); - builder.description(user.getTranslation("commands.island.team.gui.buttons.rank-filter.description")); - builder.clickHandler((panel, user, clickType, clickSlot) -> { - if (!template.actions().stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { - // If the click type is not in the template, don't do anything - return true; - } - if (clickType.equals(ClickType.LEFT)) { - rank = RanksManager.getInstance().getRankDownValue(rank); - if (rank <= RanksManager.VISITOR_RANK) { - rank = RanksManager.OWNER_RANK; - user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F); - } else { - user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); - } - } - if (clickType.equals(ClickType.RIGHT)) { - rank = RanksManager.getInstance().getRankUpValue(rank); - if (rank >= RanksManager.OWNER_RANK) { - rank = RanksManager.getInstance().getRankUpValue(RanksManager.VISITOR_RANK); - user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F); - } else { - user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); - } - } - - // Update panel after click - build(); - return true; - }); - - return builder.build(); - } - - /** - * Create invited button panel item. - * - * @param template the template - * @param slot the slot - * @return the panel item - */ - private PanelItem createInvitedButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { - PanelItemBuilder builder = new PanelItemBuilder(); - if (isInvited(user.getUniqueId()) && user.hasPermission(this.acceptCommand.getPermission())) { - Invite invite = getInvite(user.getUniqueId()); - User inviter = User.getInstance(invite.getInviter()); - String name = inviter.getName(); - builder.icon(inviter.getName()); - builder.name(user.getTranslation("commands.island.team.gui.buttons.invitation")); - builder.description(switch (invite.getType()) { - case COOP -> - List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.coop", TextVariables.NAME, - name)); - case TRUST -> - List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.trust", - TextVariables.NAME, name)); - default -> - List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you", TextVariables.NAME, - name), user.getTranslation("commands.island.team.invite.accept.confirmation")); - }); - // Add all the tool tips - builder.description(template.actions().stream() - .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") - + " " - + user.getTranslation(ar.tooltip())) - .toList()); - builder.clickHandler((panel, user, clickType, clickSlot) -> { - if (!template.actions().stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { - // If the click type is not in the template, don't do anything - return true; - } - if (clickType.equals(ClickType.SHIFT_LEFT) && user.hasPermission(this.acceptCommand.getPermission())) { - getPlugin().log("Invite accepted: " + user.getName() + " accepted " + invite.getType()); - // Accept - switch (invite.getType()) { - case COOP -> this.acceptCommand.acceptCoopInvite(user, invite); - case TRUST -> this.acceptCommand.acceptTrustInvite(user, invite); - default -> this.acceptCommand.acceptTeamInvite(user, invite); - } - user.closeInventory(); - } - if (clickType.equals(ClickType.SHIFT_RIGHT) && user.hasPermission(this.rejectCommand.getPermission())) { - // Reject - getPlugin().log("Invite rejected: " + user.getName() + " rejected " + invite.getType() - + " invite."); - this.rejectCommand.execute(user, "", List.of()); - user.closeInventory(); - } - return true; - }); - } else { - return this.getBlankBorder(); - } - return builder.build(); - } - - /** - * Create status button panel item. - * - * @param template the template - * @param slot the slot - * @return the panel item - */ - private PanelItem createStatusButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { - PanelItemBuilder builder = new PanelItemBuilder(); - // Player issuing the command must have an island - Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()); - if (island == null) { - return getBlankBorder(); - } - - return builder.icon(user.getName()).name(user.getTranslation("commands.island.team.gui.buttons.status.name")) - .description(showMembers()).build(); - } - - private PanelItem getBlankBorder() { - return new PanelItemBuilder().icon(Objects.requireNonNullElse(border.icon(), new ItemStack(Material.BARRIER))) - .name((Objects.requireNonNullElse(border.title(), ""))).build(); - } - - private PanelItem getBlankBackground() { - return new PanelItemBuilder() - .icon(Objects.requireNonNullElse(background.icon(), new ItemStack(Material.BARRIER))) - .name((Objects.requireNonNullElse(background.title(), ""))).build(); - } - - /** - * Create member button panel item. - * - * @param template the template - * @param slot the slot - * @return the panel item - */ - private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { - // Player issuing the command must have an island - Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()); - if (island == null) { - return this.getBlankBackground(); - } - return switch (rank) { - case RanksManager.OWNER_RANK -> ownerView(template, slot); - default -> getMemberButton(rank, slot.slot(), template.actions()); - }; - } - - /** - * The owner view shows all the ranks, in order - * @param template template reference - * @param slot slot to show - * @return panel item - */ - private PanelItem ownerView(ItemTemplateRecord template, ItemSlot slot) { - if (slot.slot() == 0 && island.getOwner() != null) { - // Owner - PanelItem item = getMemberButton(RanksManager.OWNER_RANK, 1, template.actions()); - if (item != null) { - return item; - } - } - long subOwnerCount = island.getMemberSet(RanksManager.SUB_OWNER_RANK, false).stream().count(); - long memberCount = island.getMemberSet(RanksManager.MEMBER_RANK, false).stream().count(); - long coopCount = island.getMemberSet(RanksManager.COOP_RANK, false).stream().count(); - long trustedCount = island.getMemberSet(RanksManager.TRUSTED_RANK, false).stream().count(); - - if (slot.slot() > 0 && slot.slot() < subOwnerCount + 1) { - // Show sub owners - PanelItem item = getMemberButton(RanksManager.SUB_OWNER_RANK, slot.slot(), template.actions()); - if (item != null) { - return item; - } - - } - if (slot.slot() > subOwnerCount && slot.slot() < subOwnerCount + memberCount + 1) { - // Show members - PanelItem item = getMemberButton(RanksManager.MEMBER_RANK, slot.slot(), template.actions()); - if (item != null) { - return item; - } - } - if (slot.slot() > subOwnerCount + memberCount && slot.slot() < subOwnerCount + memberCount + trustedCount + 1) { - // Show trusted - PanelItem item = getMemberButton(RanksManager.TRUSTED_RANK, slot.slot(), template.actions()); - if (item != null) { - return item; - } - - } - if (slot.slot() > subOwnerCount + memberCount + trustedCount - && slot.slot() < subOwnerCount + memberCount + trustedCount + coopCount + 1) { - // Show coops - return getMemberButton(RanksManager.COOP_RANK, slot.slot(), template.actions()); - } - return this.getBlankBackground(); - - } - - /** - * Shows a member's head. The clicks available will depend on who is viewing. - * @param targetRank - the rank to show - * @param slot - the slot number - * @param actions - actions that need to apply to this member button as provided by the template - * @return panel item - */ - private PanelItem getMemberButton(int targetRank, int slot, List actions) { - if (slot == 0 && island.getOwner() != null) { - // Owner - return getMemberButton(RanksManager.OWNER_RANK, 1, actions); - } - String ref = RanksManager.getInstance().getRank(targetRank); - Optional opMember = island.getMemberSet(targetRank, false).stream().sorted().skip(slot - 1L).limit(1L) - .map(User::getInstance).findFirst(); - if (opMember.isEmpty()) { - return this.getBlankBackground(); - } - User member = opMember.get(); - // Make button description depending on viewer - List desc = new ArrayList<>(); - int userRank = Objects.requireNonNull(island).getRank(user); - // Add the tooltip for kicking - if (user.hasPermission(this.kickCommand.getPermission()) - && userRank >= island.getRankCommand(this.getLabel() + " kick") && !user.equals(member)) { - actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("kick")) - .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") - + " " + user.getTranslation(ar.tooltip())) - .findFirst().ifPresent(desc::add); - } - // Set Owner - if (user.hasPermission(this.setOwnerCommand.getPermission()) && !user.equals(member) - && userRank >= RanksManager.OWNER_RANK && targetRank >= RanksManager.MEMBER_RANK) { - // Add the tooltip for setowner - actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("setowner")) - .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") - + " " + user.getTranslation(ar.tooltip())) - .findFirst().ifPresent(desc::add); - } - // Leave - if (user.hasPermission(this.leaveCommand.getPermission()) && user.equals(member) - && userRank < RanksManager.OWNER_RANK) { - // Add the tooltip for leave - actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("leave")) - .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") - + " " + user.getTranslation(ar.tooltip())) - .findFirst().ifPresent(desc::add); - } - if (member.isOnline()) { - desc.add(0, user.getTranslation(ref)); - return new PanelItemBuilder().icon(member.getName()).name(member.getDisplayName()).description(desc) - .clickHandler( - (panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member, actions)) - .build(); - } else { - // Offline player - desc.add(0, user.getTranslation(ref)); - return new PanelItemBuilder().icon(member.getName()) - .name(offlinePlayerStatus(user, Bukkit.getOfflinePlayer(member.getUniqueId()))).description(desc) - .clickHandler( - (panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member, actions)) - .build(); - } - } - - private boolean clickListener(Panel panel, User clickingUser, ClickType clickType, int i, User target, - List actions) { - if (!actions.stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { - // If the click type is not in the template, don't do anything - return true; - } - int rank = Objects.requireNonNull(island).getRank(clickingUser); - for (ItemTemplateRecord.ActionRecords action : actions) { - if (clickType.equals(action.clickType())) { - switch (action.actionType().toUpperCase(Locale.ENGLISH)) { - case "KICK" -> { - // Kick the player, or uncoop, or untrust - if (clickingUser.hasPermission(this.kickCommand.getPermission()) && !target.equals(clickingUser) - && rank >= island.getRankCommand(this.getLabel() + " kick")) { - getPlugin().log("Kick: " + clickingUser.getName() + " kicked " + target.getName() - + " from island at " + island.getCenter()); - clickingUser.closeInventory(); - if (removePlayer(clickingUser, target)) { - clickingUser.getPlayer().playSound(clickingUser.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, - 1F); - getPlugin().log("Kick: success"); - } else { - getPlugin().log("Kick: failed"); - } - } - } - case "SETOWNER" -> { - // Make the player the leader of the island - if (clickingUser.hasPermission(this.setOwnerCommand.getPermission()) && !target.equals(clickingUser) - && clickingUser.getUniqueId().equals(island.getOwner()) - && island.getRank(target) >= RanksManager.MEMBER_RANK) { - getPlugin().log("Set Owner: " + clickingUser.getName() + " trying to make " + target.getName() - + " owner of island at " + island.getCenter()); - clickingUser.closeInventory(); - if (this.setOwnerCommand.setOwner(clickingUser, target.getUniqueId())) { - getPlugin().log("Set Owner: success"); - } else { - getPlugin().log("Set Owner: failed"); - } - } - } - case "LEAVE" -> { - if (clickingUser.hasPermission(this.leaveCommand.getPermission()) && target.equals(clickingUser) - && !clickingUser.getUniqueId().equals(island.getOwner())) { - getPlugin().log("Leave: " + clickingUser.getName() + " trying to leave island at " - + island.getCenter()); - clickingUser.closeInventory(); - if (leaveCommand.leave(clickingUser)) { - getPlugin().log("Leave: success"); - } else { - getPlugin().log("Leave: failed"); - } - } - } - } - } - } - return true; - } - - private boolean removePlayer(User clicker, User member) { - // If member then kick, if coop, uncoop, if trusted, then untrust - return switch (island.getRank(member)) { - case RanksManager.COOP_RANK -> this.uncoopCommand.unCoopCmd(user, member.getUniqueId()); - case RanksManager.TRUSTED_RANK -> this.unTrustCommand.unTrustCmd(user, member.getUniqueId()); - default -> { - if (kickCommand.canExecute(user, kickCommand.getLabel(), List.of(member.getName()))) { - yield kickCommand.kick(clicker, member.getUniqueId()); - } else { - yield false; - } - } - }; - - } - - private List showMembers() { - List message = new ArrayList<>(); - // Gather online members - long onlineMemberCount = island.getMemberSet(RanksManager.MEMBER_RANK).stream() - .filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName())) - .count(); - - // Show header: - message.add(user.getTranslation("commands.island.team.info.header", "[max]", - String.valueOf(getIslands().getMaxMembers(island, RanksManager.MEMBER_RANK)), "[total]", - String.valueOf(island.getMemberSet().size()), "[online]", String.valueOf(onlineMemberCount))); - - // We now need to get all online "members" of the island - incl. Trusted and coop - List onlineMembers = island.getMemberSet(RanksManager.COOP_RANK).stream() - .filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName())) - .toList(); - - for (int rank : RANKS) { - Set players = island.getMemberSet(rank, false); - if (!players.isEmpty()) { - if (rank == RanksManager.OWNER_RANK) { - // Slightly special handling for the owner rank - message.add(user.getTranslation("commands.island.team.info.rank-layout.owner", TextVariables.RANK, - user.getTranslation(RanksManager.OWNER_RANK_REF))); - } else { - message.add(user.getTranslation("commands.island.team.info.rank-layout.generic", TextVariables.RANK, - user.getTranslation(RanksManager.getInstance().getRank(rank)), TextVariables.NUMBER, - String.valueOf(island.getMemberSet(rank, false).size()))); - } - message.addAll(displayOnOffline(user, rank, island, onlineMembers)); - } - } - return message; - } - - private List displayOnOffline(User user, int rank, Island island, List onlineMembers) { - List message = new ArrayList<>(); - for (UUID member : island.getMemberSet(rank, false)) { - message.add(getMemberStatus(user, member, onlineMembers.contains(member))); - - } - return message; - } - - private String getMemberStatus(User user2, UUID member, boolean online) { - OfflinePlayer offlineMember = Bukkit.getOfflinePlayer(member); - if (online) { - return user.getTranslation("commands.island.team.info.member-layout.online", TextVariables.NAME, - offlineMember.getName()); - } else { - return offlinePlayerStatus(user, offlineMember); - } - } - - /** - * Creates text to describe the status of the player - * @param user2 user asking to see the status - * @param offlineMember member of the team - * @return string - */ - private String offlinePlayerStatus(User user2, OfflinePlayer offlineMember) { - String lastSeen = lastSeen(offlineMember); - if (island.getMemberSet(RanksManager.MEMBER_RANK, true).contains(offlineMember.getUniqueId())) { - return user.getTranslation("commands.island.team.info.member-layout.offline", TextVariables.NAME, - offlineMember.getName(), "[last_seen]", lastSeen); - } else { - // This will prevent anyone that is trusted or below to not have a last-seen status - return user.getTranslation("commands.island.team.info.member-layout.offline-not-last-seen", - TextVariables.NAME, offlineMember.getName()); - } - } - - private String lastSeen(OfflinePlayer offlineMember) { - // A bit of handling for the last joined date - Instant lastJoined = Instant.ofEpochMilli(offlineMember.getLastPlayed()); - Instant now = Instant.now(); - - Duration duration = Duration.between(lastJoined, now); - String lastSeen; - final String reference = "commands.island.team.info.last-seen.layout"; - if (duration.toMinutes() < 60L) { - lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toMinutes()), - TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.minutes")); - } else if (duration.toHours() < 24L) { - lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toHours()), - TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.hours")); - } else { - lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toDays()), - TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.days")); - } - return lastSeen; - } - private boolean fireEvent(User user, Island island) { IslandBaseEvent e = TeamEvent.builder().island(island).reason(TeamEvent.Reason.INFO) .involvedPlayer(user.getUniqueId()).build(); @@ -731,4 +197,52 @@ public class IslandTeamCommand extends CompositeCommand { protected IslandTeamTrustCommand getTrustCommand() { return trustCommand; } + + public IslandTeamInviteCommand getInviteCommand() { + return inviteCommand; + } + + public IslandTeamInviteAcceptCommand getAcceptCommand() { + return acceptCommand; + } + + public IslandTeamInviteRejectCommand getRejectCommand() { + return rejectCommand; + } + + /** + * @return the kickCommand + */ + public IslandTeamKickCommand getKickCommand() { + return kickCommand; + } + + /** + * @return the leaveCommand + */ + public IslandTeamLeaveCommand getLeaveCommand() { + return leaveCommand; + } + + /** + * @return the setOwnerCommand + */ + public IslandTeamSetownerCommand getSetOwnerCommand() { + return setOwnerCommand; + } + + /** + * @return the uncoopCommand + */ + public IslandTeamUncoopCommand getUncoopCommand() { + return uncoopCommand; + } + + /** + * @return the unTrustCommand + */ + public IslandTeamUntrustCommand getUnTrustCommand() { + return unTrustCommand; + } + } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java new file mode 100644 index 000000000..f60ef8d5a --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java @@ -0,0 +1,536 @@ +package world.bentobox.bentobox.api.commands.island.team; + +import java.io.File; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.Sound; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.panels.Panel; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.TemplatedPanel; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords; +import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.RanksManager; +import world.bentobox.bentobox.util.Util; + +public class IslandTeamGUI { + + /** + * List of ranks that we will loop through in order + */ + private static final List RANKS = List.of(RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK, + RanksManager.MEMBER_RANK, RanksManager.TRUSTED_RANK, RanksManager.COOP_RANK); + + private final User user; + + private final Island island; + + private int rankView = RanksManager.OWNER_RANK; + + private @Nullable TemplateItem border; + + private @Nullable TemplateItem background; + + private final IslandTeamCommand parent; + + private final BentoBox plugin; + + + /** + * Displays the team management GUI + * @param plugin BentoBox + * @param parent IslandTeamCommand object + * @param user user who is opening the GUI + * @param island island that the GUI is managing + */ + public IslandTeamGUI(BentoBox plugin, IslandTeamCommand parent, User user, Island island) { + this.parent = parent; + this.plugin = plugin; + this.user = user; + this.island = island; + // Panels + if (!new File(plugin.getDataFolder() + File.separator + "panels", "team_panel.yml").exists()) { + plugin.saveResource("panels/team_panel.yml", false); + } + } + + /** + * This method builds this GUI. + */ + public void build() { + // Start building panel. + TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); + panelBuilder.user(user); + panelBuilder.world(user.getWorld()); + + panelBuilder.template("team_panel", new File(plugin.getDataFolder(), "panels")); + + panelBuilder.parameters("[name]", user.getName(), "[display_name]", user.getDisplayName()); + + panelBuilder.registerTypeBuilder("STATUS", this::createStatusButton); + panelBuilder.registerTypeBuilder("MEMBER", this::createMemberButton); + panelBuilder.registerTypeBuilder("INVITED", this::createInvitedButton); + panelBuilder.registerTypeBuilder("RANK", this::createRankButton); + panelBuilder.registerTypeBuilder("INVITE", this::createInviteButton); + border = panelBuilder.getPanelTemplate().border(); + background = panelBuilder.getPanelTemplate().background(); + // Register unknown type builder. + panelBuilder.build(); + } + + private PanelItem createInviteButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + if (island == null || !user.hasPermission(this.parent.getInviteCommand().getPermission()) + || island.getRank(user) < island.getRankCommand(parent.getLabel() + " invite")) { + return this.getBlankBorder(); + } + PanelItemBuilder builder = new PanelItemBuilder(); + builder.icon(Material.PLAYER_HEAD); + builder.name(user.getTranslation("commands.island.team.gui.buttons.invite.name")); + builder.description(user.getTranslation("commands.island.team.gui.buttons.invite.description")); + builder.clickHandler((panel, user, clickType, clickSlot) -> { + if (!template.actions().stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { + // If the click type is not in the template, don't do anything + return true; + } + if (clickType.equals(ClickType.LEFT)) { + user.closeInventory(); + new IslandTeamInviteGUI(parent, false, island).build(user); + } + return true; + }); + return builder.build(); + } + + private PanelItem createRankButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + // If there is no island, the do not show this icon + if (island == null) { + return this.getBlankBorder(); + } + PanelItemBuilder builder = new PanelItemBuilder(); + builder.name(user.getTranslation("commands.island.team.gui.buttons.rank-filter.name")); + builder.icon(Material.AMETHYST_SHARD); + // Create description + RanksManager.getInstance().getRanks().forEach((reference, score) -> { + if (rankView == RanksManager.OWNER_RANK && score > RanksManager.VISITOR_RANK + && score <= RanksManager.OWNER_RANK) { + builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank") + + user.getTranslation(reference)); + } else if (score > RanksManager.VISITOR_RANK && score < rankView) { + builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank") + + user.getTranslation(reference)); + } else if (score <= RanksManager.OWNER_RANK && score > rankView) { + builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank") + + user.getTranslation(reference)); + } else if (score == rankView) { + builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank") + + user.getTranslation(reference)); + } + }); + builder.description(user.getTranslation("commands.island.team.gui.buttons.rank-filter.description")); + builder.clickHandler((panel, user, clickType, clickSlot) -> { + if (!template.actions().stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { + // If the click type is not in the template, don't do anything + return true; + } + if (clickType.equals(ClickType.LEFT)) { + rankView = RanksManager.getInstance().getRankDownValue(rankView); + if (rankView <= RanksManager.VISITOR_RANK) { + rankView = RanksManager.OWNER_RANK; + user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F); + } else { + user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); + } + } + if (clickType.equals(ClickType.RIGHT)) { + rankView = RanksManager.getInstance().getRankUpValue(rankView); + if (rankView >= RanksManager.OWNER_RANK) { + rankView = RanksManager.getInstance().getRankUpValue(RanksManager.VISITOR_RANK); + user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F); + } else { + user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); + } + } + + // Update panel after click + build(); + return true; + }); + + return builder.build(); + } + + /** + * Create invited button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createInvitedButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + PanelItemBuilder builder = new PanelItemBuilder(); + if (parent.isInvited(user.getUniqueId()) && user.hasPermission(parent.getAcceptCommand().getPermission())) { + Invite invite = parent.getInvite(user.getUniqueId()); + User inviter = User.getInstance(invite.getInviter()); + String name = inviter.getName(); + builder.icon(inviter.getName()); + builder.name(user.getTranslation("commands.island.team.gui.buttons.invitation")); + builder.description(switch (invite.getType()) { + case COOP -> + List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.coop", TextVariables.NAME, + name)); + case TRUST -> + List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.trust", + TextVariables.NAME, name)); + default -> + List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you", TextVariables.NAME, + name), user.getTranslation("commands.island.team.invite.accept.confirmation")); + }); + // Add all the tool tips + builder.description(template.actions().stream() + .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") + + " " + + user.getTranslation(ar.tooltip())) + .toList()); + builder.clickHandler((panel, user, clickType, clickSlot) -> { + if (!template.actions().stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { + // If the click type is not in the template, don't do anything + return true; + } + if (clickType.equals(ClickType.SHIFT_LEFT) + && user.hasPermission(parent.getAcceptCommand().getPermission())) { + plugin.log("Invite accepted: " + user.getName() + " accepted " + invite.getType()); + // Accept + switch (invite.getType()) { + case COOP -> parent.getAcceptCommand().acceptCoopInvite(user, invite); + case TRUST -> parent.getAcceptCommand().acceptTrustInvite(user, invite); + default -> parent.getAcceptCommand().acceptTeamInvite(user, invite); + } + user.closeInventory(); + } + if (clickType.equals(ClickType.SHIFT_RIGHT) + && user.hasPermission(parent.getRejectCommand().getPermission())) { + // Reject + plugin.log("Invite rejected: " + user.getName() + " rejected " + invite.getType() + + " invite."); + parent.getRejectCommand().execute(user, "", List.of()); + user.closeInventory(); + } + return true; + }); + } else { + return this.getBlankBorder(); + } + return builder.build(); + } + + /** + * Create status button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createStatusButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + PanelItemBuilder builder = new PanelItemBuilder(); + // Player issuing the command must have an island + Island island = plugin.getIslands().getPrimaryIsland(parent.getWorld(), user.getUniqueId()); + if (island == null) { + return getBlankBorder(); + } + + return builder.icon(user.getName()).name(user.getTranslation("commands.island.team.gui.buttons.status.name")) + .description(showMembers()).build(); + } + + private PanelItem getBlankBorder() { + return new PanelItemBuilder().icon(Objects.requireNonNullElse(border.icon(), new ItemStack(Material.BARRIER))) + .name((Objects.requireNonNullElse(border.title(), ""))).build(); + } + + private PanelItem getBlankBackground() { + return new PanelItemBuilder() + .icon(Objects.requireNonNullElse(background.icon(), new ItemStack(Material.BARRIER))) + .name((Objects.requireNonNullElse(background.title(), ""))).build(); + } + + /** + * Create member button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + // Player issuing the command must have an island + Island island = plugin.getIslands().getPrimaryIsland(parent.getWorld(), user.getUniqueId()); + if (island == null) { + return this.getBlankBackground(); + } + + Optional opMember = island.getMemberSet().stream().map(User::getInstance) + .filter((User usr) -> rankView == RanksManager.OWNER_RANK || island.getRank(usr) == rankView) // If rankView is owner then show all ranks + .sorted(Comparator.comparingInt((User usr) -> island.getRank(usr)).reversed()) // Show owner on left, then descending ranks + .skip(slot.slot()) // Get the head for this slot + .limit(1L).findFirst(); // Get just one head + if (opMember.isEmpty()) { + return this.getBlankBackground(); + } + User member = opMember.get(); + int rank = island.getRank(member); + String rankRef = RanksManager.getInstance().getRank(rank); + @NonNull + List actions = template.actions(); + // Make button description depending on viewer + List desc = new ArrayList<>(); + int userRank = Objects.requireNonNull(island).getRank(user); + // Add the tooltip for kicking + if (user.hasPermission(parent.getKickCommand().getPermission()) + && userRank >= island.getRankCommand(parent.getLabel() + " kick") && !user.equals(member)) { + actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("kick")) + .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") + + " " + user.getTranslation(ar.tooltip())) + .findFirst().ifPresent(desc::add); + } + // Set Owner + if (user.hasPermission(parent.getSetOwnerCommand().getPermission()) && !user.equals(member) + && userRank >= RanksManager.OWNER_RANK && rank >= RanksManager.MEMBER_RANK) { + // Add the tooltip for setowner + actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("setowner")) + .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") + + " " + user.getTranslation(ar.tooltip())) + .findFirst().ifPresent(desc::add); + } + // Leave + if (user.hasPermission(parent.getLeaveCommand().getPermission()) && user.equals(member) + && userRank < RanksManager.OWNER_RANK) { + // Add the tooltip for leave + actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("leave")) + .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") + + " " + user.getTranslation(ar.tooltip())) + .findFirst().ifPresent(desc::add); + } + if (member.isOnline()) { + desc.add(0, user.getTranslation(rankRef)); + return new PanelItemBuilder().icon(member.getName()).name(member.getDisplayName()).description(desc) + .clickHandler( + (panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member, actions)) + .build(); + } else { + // Offline player + desc.add(0, user.getTranslation(rankRef)); + return new PanelItemBuilder().icon(member.getName()) + .name(offlinePlayerStatus(user, Bukkit.getOfflinePlayer(member.getUniqueId()))).description(desc) + .clickHandler( + (panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member, actions)) + .build(); + } + } + + /** + * Click listener + * @param panel panel + * @param clickingUser clicking user + * @param clickType click type + * @param i slot + * @param target target user + * @param actions actions + * @return true if the inventory item should not be removed - always true + */ + private boolean clickListener(Panel panel, User clickingUser, ClickType clickType, int i, User target, + List actions) { + if (!actions.stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { + // If the click type is not in the template, don't do anything + return true; + } + int rank = Objects.requireNonNull(island).getRank(clickingUser); + for (ItemTemplateRecord.ActionRecords action : actions) { + if (clickType.equals(action.clickType())) { + switch (action.actionType().toUpperCase(Locale.ENGLISH)) { + case "KICK" -> { + // Kick the player, or uncoop, or untrust + if (clickingUser.hasPermission(parent.getKickCommand().getPermission()) + && !target.equals(clickingUser) + && rank >= island.getRankCommand(parent.getLabel() + " kick")) { + plugin.log("Kick: " + clickingUser.getName() + " kicked " + target.getName() + + " from island at " + island.getCenter()); + clickingUser.closeInventory(); + if (removePlayer(clickingUser, target)) { + clickingUser.getPlayer().playSound(clickingUser.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, + 1F); + plugin.log("Kick: success"); + } else { + plugin.log("Kick: failed"); + } + } + } + case "SETOWNER" -> { + // Make the player the leader of the island + if (clickingUser.hasPermission(parent.getSetOwnerCommand().getPermission()) + && !target.equals(clickingUser) + && clickingUser.getUniqueId().equals(island.getOwner()) + && island.getRank(target) >= RanksManager.MEMBER_RANK) { + plugin.log("Set Owner: " + clickingUser.getName() + " trying to make " + target.getName() + + " owner of island at " + island.getCenter()); + clickingUser.closeInventory(); + if (parent.getSetOwnerCommand().setOwner(clickingUser, target.getUniqueId())) { + plugin.log("Set Owner: success"); + } else { + plugin.log("Set Owner: failed"); + } + } + } + case "LEAVE" -> { + if (clickingUser.hasPermission(parent.getLeaveCommand().getPermission()) + && target.equals(clickingUser) + && !clickingUser.getUniqueId().equals(island.getOwner())) { + plugin.log("Leave: " + clickingUser.getName() + " trying to leave island at " + + island.getCenter()); + clickingUser.closeInventory(); + if (parent.getLeaveCommand().leave(clickingUser)) { + plugin.log("Leave: success"); + } else { + plugin.log("Leave: failed"); + } + } + } + } + } + } + return true; + } + + private boolean removePlayer(User clicker, User member) { + // If member then kick, if coop, uncoop, if trusted, then untrust + return switch (island.getRank(member)) { + case RanksManager.COOP_RANK -> parent.getUncoopCommand().unCoopCmd(user, member.getUniqueId()); + case RanksManager.TRUSTED_RANK -> parent.getUnTrustCommand().unTrustCmd(user, member.getUniqueId()); + default -> { + if (parent.getKickCommand().canExecute(user, parent.getKickCommand().getLabel(), + List.of(member.getName()))) { + yield parent.getKickCommand().kick(clicker, member.getUniqueId()); + } else { + yield false; + } + } + }; + + } + + private List showMembers() { + List message = new ArrayList<>(); + // Gather online members + long onlineMemberCount = island.getMemberSet(RanksManager.MEMBER_RANK).stream() + .filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName())) + .count(); + + // Show header: + message.add(user.getTranslation("commands.island.team.info.header", "[max]", + String.valueOf(plugin.getIslands().getMaxMembers(island, RanksManager.MEMBER_RANK)), "[total]", + String.valueOf(island.getMemberSet().size()), "[online]", String.valueOf(onlineMemberCount))); + + // We now need to get all online "members" of the island - incl. Trusted and coop + List onlineMembers = island.getMemberSet(RanksManager.COOP_RANK).stream() + .filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName())) + .toList(); + + for (int rank : RANKS) { + Set players = island.getMemberSet(rank, false); + if (!players.isEmpty()) { + if (rank == RanksManager.OWNER_RANK) { + // Slightly special handling for the owner rank + message.add(user.getTranslation("commands.island.team.info.rank-layout.owner", TextVariables.RANK, + user.getTranslation(RanksManager.OWNER_RANK_REF))); + } else { + message.add(user.getTranslation("commands.island.team.info.rank-layout.generic", TextVariables.RANK, + user.getTranslation(RanksManager.getInstance().getRank(rank)), TextVariables.NUMBER, + String.valueOf(island.getMemberSet(rank, false).size()))); + } + message.addAll(displayOnOffline(user, rank, island, onlineMembers)); + } + } + return message; + } + + private List displayOnOffline(User user, int rank, Island island, List onlineMembers) { + List message = new ArrayList<>(); + for (UUID member : island.getMemberSet(rank, false)) { + message.add(getMemberStatus(user, member, onlineMembers.contains(member))); + + } + return message; + } + + private String getMemberStatus(User user2, UUID member, boolean online) { + OfflinePlayer offlineMember = Bukkit.getOfflinePlayer(member); + if (online) { + return user.getTranslation("commands.island.team.info.member-layout.online", TextVariables.NAME, + offlineMember.getName()); + } else { + return offlinePlayerStatus(user, offlineMember); + } + } + + /** + * Creates text to describe the status of the player + * @param user2 user asking to see the status + * @param offlineMember member of the team + * @return string + */ + private String offlinePlayerStatus(User user2, OfflinePlayer offlineMember) { + String lastSeen = lastSeen(offlineMember); + if (island.getMemberSet(RanksManager.MEMBER_RANK, true).contains(offlineMember.getUniqueId())) { + return user.getTranslation("commands.island.team.info.member-layout.offline", TextVariables.NAME, + offlineMember.getName(), "[last_seen]", lastSeen); + } else { + // This will prevent anyone that is trusted or below to not have a last-seen status + return user.getTranslation("commands.island.team.info.member-layout.offline-not-last-seen", + TextVariables.NAME, offlineMember.getName()); + } + } + + private String lastSeen(OfflinePlayer offlineMember) { + // A bit of handling for the last joined date + Instant lastJoined = Instant.ofEpochMilli(offlineMember.getLastPlayed()); + Instant now = Instant.now(); + + Duration duration = Duration.between(lastJoined, now); + String lastSeen; + final String reference = "commands.island.team.info.last-seen.layout"; + if (duration.toMinutes() < 60L) { + lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toMinutes()), + TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.minutes")); + } else if (duration.toHours() < 24L) { + lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toHours()), + TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.hours")); + } else { + lastSeen = user.getTranslation(reference, TextVariables.NUMBER, String.valueOf(duration.toDays()), + TextVariables.UNIT, user.getTranslation("commands.island.team.info.last-seen.days")); + } + return lastSeen; + } + + +} \ No newline at end of file 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 976d66147..c291e65ca 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 @@ -7,28 +7,13 @@ import java.util.Objects; import java.util.Optional; import java.util.UUID; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.conversations.ConversationFactory; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.commands.island.team.Invite.Type; import world.bentobox.bentobox.api.events.IslandBaseEvent; import world.bentobox.bentobox.api.events.team.TeamEvent; import world.bentobox.bentobox.api.localization.TextVariables; -import world.bentobox.bentobox.api.panels.Panel; -import world.bentobox.bentobox.api.panels.PanelItem; -import world.bentobox.bentobox.api.panels.TemplatedPanel; -import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; -import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder; -import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; -import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords; import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; @@ -43,11 +28,6 @@ public class IslandTeamInviteCommand extends CompositeCommand { private @Nullable User invitedPlayer; private @Nullable TemplateItem border; private @Nullable TemplateItem background; - private User user; - private long page = 0; // This number by 35 - private boolean inviteCmd; - private static final long PER_PAGE = 35; - private String searchName = ""; public IslandTeamInviteCommand(IslandTeamCommand parent) { super(parent, "invite"); @@ -77,14 +57,13 @@ public class IslandTeamInviteCommand extends CompositeCommand { user.sendMessage("general.errors.no-island"); return false; } + Island island = islandsManager.getIsland(getWorld(), user); if (args.size() != 1) { - this.inviteCmd = true; - build(user); + new IslandTeamInviteGUI(itc, true, island).build(user); return true; } - Island island = islandsManager.getIsland(getWorld(), user); int rank = Objects.requireNonNull(island).getRank(user); return checkRankAndInvitePlayer(user, island, rank, args.get(0)); @@ -208,204 +187,4 @@ public class IslandTeamInviteCommand extends CompositeCommand { return Optional.of(Util.tabLimit(options, lastArg)); } - /** - * Build the invite panel - * @param user use of the panel - */ - void build(User user) { - this.user = user; - // Start building panel. - TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); - panelBuilder.user(user); - panelBuilder.world(user.getWorld()); - - panelBuilder.template("team_invite_panel", new File(getPlugin().getDataFolder(), "panels")); - - panelBuilder.parameters("[name]", user.getName(), "[display_name]", user.getDisplayName()); - - panelBuilder.registerTypeBuilder("PROSPECT", this::createProspectButton); - panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton); - panelBuilder.registerTypeBuilder("NEXT", this::createNextButton); - panelBuilder.registerTypeBuilder("SEARCH", this::createSearchButton); - panelBuilder.registerTypeBuilder("BACK", this::createBackButton); - // Stash the backgrounds for later use - border = panelBuilder.getPanelTemplate().border(); - background = panelBuilder.getPanelTemplate().background(); - // Register unknown type builder. - panelBuilder.build(); - - } - - private PanelItem createBackButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { - checkTemplate(template); - return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon()) - .clickHandler((panel, user, clickType, clickSlot) -> { - user.closeInventory(); - if (!inviteCmd) { - this.itc.build(); - } - return true; - }).build(); - } - - private PanelItem createSearchButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { - checkTemplate(template); - PanelItemBuilder pib = new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon()) - .clickHandler((panel, user, clickType, clickSlot) -> { - user.closeInventory(); - new ConversationFactory(BentoBox.getInstance()).withLocalEcho(false).withTimeout(90) - .withModality(false).withFirstPrompt(new InviteNamePrompt(user, this)) - .buildConversation(user.getPlayer()).begin(); - return true; - }); - if (!this.searchName.isBlank()) { - pib.description(user.getTranslation(Objects - .requireNonNullElse(template.description(), - "commands.island.team.invite.gui.button.searching"), - TextVariables.NAME, searchName)); - } - return pib.build(); - } - - private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { - checkTemplate(template); - long count = getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player)) - .filter(player -> !player.equals(user.getPlayer())).count(); - if (count > page * PER_PAGE) { - // We need to show a next button - return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon()) - .clickHandler((panel, user, clickType, clickSlot) -> { - user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); - page++; - build(user); - return true; - }).build(); - } - return getBlankBorder(); - } - - private void checkTemplate(ItemTemplateRecord template) { - if (template.icon() == null) { - getPlugin().logError("Icon in template is missing or unknown! " + template.toString()); - } - if (template.title() == null) { - getPlugin().logError("Title in template is missing! " + template.toString()); - } - - } - - private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { - checkTemplate(template); - if (page > 0) { - // We need to show a next button - return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon()) - .clickHandler((panel, user, clickType, clickSlot) -> { - user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); - page--; - build(user); - return true; - }).build(); - } - return getBlankBorder(); - } - - private PanelItem getBlankBorder() { - return new PanelItemBuilder().icon(Objects.requireNonNullElse(border.icon(), new ItemStack(Material.BARRIER))) - .name((Objects.requireNonNullElse(border.title(), ""))).build(); - } - - /** - * Create member button panel item. - * - * @param template the template - * @param slot the slot - * @return the panel item - */ - private PanelItem createProspectButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { - // Player issuing the command must have an island - Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId()); - if (island == null) { - return this.getBlankBackground(); - } - if (page < 0) { - page = 0; - } - return getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player)) - .filter(player -> this.searchName.isBlank() ? true - : player.getName().toLowerCase().contains(searchName.toLowerCase())) - .filter(player -> !player.equals(user.getPlayer())).skip(slot.slot() + page * PER_PAGE).findFirst() - .map(player -> getProspect(player, template)).orElse(this.getBlankBackground()); - } - - private PanelItem getProspect(Player player, ItemTemplateRecord template) { - // Check if the prospect has already been invited - if (this.itc.isInvited(player.getUniqueId()) - && user.getUniqueId().equals(this.itc.getInvite(player.getUniqueId()).getInviter())) { - return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName()) - .description(user.getTranslation("commands.island.team.invite.gui.button.already-invited")).build(); - } - List desc = template.actions().stream().map(ar -> user - .getTranslation("commands.island.team.invite.gui.tips." + ar.clickType().name() + ".name") - + " " + user.getTranslation(ar.tooltip())).toList(); - return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName()).description(desc) - .clickHandler( - (panel, user, clickType, clickSlot) -> clickHandler(panel, user, clickType, clickSlot, player, - template.actions())) - .build(); - } - - private boolean clickHandler(Panel panel, User user, ClickType clickType, int clickSlot, Player player, - @NonNull List list) { - if (!list.stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { - // If the click type is not in the template, don't do anything - return true; - } - if (clickType.equals(ClickType.LEFT)) { - user.closeInventory(); - if (this.canExecute(user, this.getLabel(), List.of(player.getName()))) { - getPlugin().log("Invite sent to: " + player.getName() + " by " + user.getName() + " to join island in " - + getWorld().getName()); - this.execute(user, getLabel(), List.of(player.getName())); - } else { - getPlugin().log("Invite failed: " + player.getName() + " by " + user.getName() + " to join island in " - + getWorld().getName()); - } - } else if (clickType.equals(ClickType.RIGHT)) { - user.closeInventory(); - if (this.itc.getCoopCommand().canExecute(user, this.getLabel(), List.of(player.getName()))) { - getPlugin().log("Coop: " + player.getName() + " cooped " + user.getName() + " to island in " - + getWorld().getName()); - this.itc.getCoopCommand().execute(user, getLabel(), List.of(player.getName())); - } else { - getPlugin().log( - "Coop failed: " + player.getName() + "'s coop to " + user.getName() + " failed for island in " - + getWorld().getName()); - } - } else if (clickType.equals(ClickType.SHIFT_LEFT)) { - user.closeInventory(); - if (this.itc.getTrustCommand().canExecute(user, this.getLabel(), List.of(player.getName()))) { - getPlugin().log("Trust: " + player.getName() + " trusted " + user.getName() + " to island in " - + getWorld().getName()); - this.itc.getTrustCommand().execute(user, getLabel(), List.of(player.getName())); - } else { - getPlugin().log("Trust failed: " + player.getName() + "'s trust failed for " + user.getName() - + " for island in " - + getWorld().getName()); - } - } - return true; - } - - private PanelItem getBlankBackground() { - return new PanelItemBuilder() - .icon(Objects.requireNonNullElse(background.icon(), new ItemStack(Material.BARRIER))) - .name((Objects.requireNonNullElse(background.title(), ""))).build(); - } - - /** - * @param searchName the searchName to set - */ - void setSearchName(String searchName) { - this.searchName = searchName; - } } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java new file mode 100644 index 000000000..dffa8ed26 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java @@ -0,0 +1,279 @@ +package world.bentobox.bentobox.api.commands.island.team; + +import java.io.File; +import java.util.List; +import java.util.Objects; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.ConversationFactory; +import org.bukkit.conversations.Prompt; +import org.bukkit.conversations.StringPrompt; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.panels.Panel; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.TemplatedPanel; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords; +import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; + +public class IslandTeamInviteGUI { + + private final IslandTeamInviteCommand itic; + private final IslandTeamCommand itc; + private @Nullable TemplateItem border; + private @Nullable TemplateItem background; + private User user; + private long page = 0; // This number by 35 + private final boolean inviteCmd; + private static final long PER_PAGE = 35; + private String searchName = ""; + private final BentoBox plugin; + private final Island island; + + public IslandTeamInviteGUI(IslandTeamCommand itc, boolean invitedCmd, Island island) { + this.island = island; + this.plugin = itc.getPlugin(); + this.inviteCmd = invitedCmd; + itic = itc.getInviteCommand(); + this.itc = itc; + // Panels + if (!new File(plugin.getDataFolder() + File.separator + "panels", "team_invite_panel.yml") + .exists()) { + plugin.saveResource("panels/team_invite_panel.yml", false); + } + } + + /** + * Build the invite panel + * @param user use of the panel + */ + void build(User user) { + this.user = user; + // Start building panel. + TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); + panelBuilder.user(user); + panelBuilder.world(user.getWorld()); + + panelBuilder.template("team_invite_panel", new File(plugin.getDataFolder(), "panels")); + + panelBuilder.parameters("[name]", user.getName(), "[display_name]", user.getDisplayName()); + + panelBuilder.registerTypeBuilder("PROSPECT", this::createProspectButton); + panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton); + panelBuilder.registerTypeBuilder("NEXT", this::createNextButton); + panelBuilder.registerTypeBuilder("SEARCH", this::createSearchButton); + panelBuilder.registerTypeBuilder("BACK", this::createBackButton); + // Stash the backgrounds for later use + border = panelBuilder.getPanelTemplate().border(); + background = panelBuilder.getPanelTemplate().background(); + // Register unknown type builder. + panelBuilder.build(); + + } + + private PanelItem createBackButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + checkTemplate(template); + return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon()) + .clickHandler((panel, user, clickType, clickSlot) -> { + user.closeInventory(); + if (!inviteCmd) { + new IslandTeamGUI(plugin, itc, user, island).build(); + } + return true; + }).build(); + } + + private PanelItem createSearchButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + checkTemplate(template); + PanelItemBuilder pib = new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon()) + .clickHandler((panel, user, clickType, clickSlot) -> { + user.closeInventory(); + new ConversationFactory(BentoBox.getInstance()).withLocalEcho(false).withTimeout(90) + .withModality(false).withFirstPrompt(new InviteNamePrompt()) + .buildConversation(user.getPlayer()).begin(); + return true; + }); + if (!this.searchName.isBlank()) { + pib.description(user.getTranslation(Objects + .requireNonNullElse(template.description(), + "commands.island.team.invite.gui.button.searching"), + TextVariables.NAME, searchName)); + } + return pib.build(); + } + + private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + checkTemplate(template); + long count = itic.getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player)) + .filter(player -> !player.equals(user.getPlayer())).count(); + if (count > page * PER_PAGE) { + // We need to show a next button + return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon()) + .clickHandler((panel, user, clickType, clickSlot) -> { + user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); + page++; + build(user); + return true; + }).build(); + } + return getBlankBorder(); + } + + private void checkTemplate(ItemTemplateRecord template) { + if (template.icon() == null) { + plugin.logError("Icon in template is missing or unknown! " + template.toString()); + } + if (template.title() == null) { + plugin.logError("Title in template is missing! " + template.toString()); + } + + } + + private PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + checkTemplate(template); + if (page > 0) { + // We need to show a next button + return new PanelItemBuilder().name(user.getTranslation(template.title())).icon(template.icon()) + .clickHandler((panel, user, clickType, clickSlot) -> { + user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); + page--; + build(user); + return true; + }).build(); + } + return getBlankBorder(); + } + + private PanelItem getBlankBorder() { + return new PanelItemBuilder().icon(Objects.requireNonNullElse(border.icon(), new ItemStack(Material.BARRIER))) + .name((Objects.requireNonNullElse(border.title(), ""))).build(); + } + + /** + * Create member button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + private PanelItem createProspectButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { + // Player issuing the command must have an island + Island island = plugin.getIslands().getPrimaryIsland(itic.getWorld(), user.getUniqueId()); + if (island == null) { + return this.getBlankBackground(); + } + if (page < 0) { + page = 0; + } + return itic.getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player)) + .filter(player -> this.searchName.isBlank() ? true + : player.getName().toLowerCase().contains(searchName.toLowerCase())) + .filter(player -> !player.equals(user.getPlayer())).skip(slot.slot() + page * PER_PAGE).findFirst() + .map(player -> getProspect(player, template)).orElse(this.getBlankBackground()); + } + + private PanelItem getProspect(Player player, ItemTemplateRecord template) { + // Check if the prospect has already been invited + if (this.itc.isInvited(player.getUniqueId()) + && user.getUniqueId().equals(this.itc.getInvite(player.getUniqueId()).getInviter())) { + return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName()) + .description(user.getTranslation("commands.island.team.invite.gui.button.already-invited")).build(); + } + List desc = template.actions().stream().map(ar -> user + .getTranslation("commands.island.team.invite.gui.tips." + ar.clickType().name() + ".name") + + " " + user.getTranslation(ar.tooltip())).toList(); + return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName()).description(desc) + .clickHandler( + (panel, user, clickType, clickSlot) -> clickHandler(panel, user, clickType, clickSlot, player, + template.actions())) + .build(); + } + + private boolean clickHandler(Panel panel, User user, ClickType clickType, int clickSlot, Player player, + @NonNull List list) { + if (!list.stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { + // If the click type is not in the template, don't do anything + return true; + } + if (clickType.equals(ClickType.LEFT)) { + user.closeInventory(); + if (itic.canExecute(user, itic.getLabel(), List.of(player.getName()))) { + plugin.log("Invite sent to: " + player.getName() + " by " + user.getName() + " to join island in " + + itic.getWorld().getName()); + itic.execute(user, itic.getLabel(), List.of(player.getName())); + } else { + plugin.log("Invite failed: " + player.getName() + " by " + user.getName() + " to join island in " + + itic.getWorld().getName()); + } + } else if (clickType.equals(ClickType.RIGHT)) { + user.closeInventory(); + if (this.itc.getCoopCommand().canExecute(user, itic.getLabel(), List.of(player.getName()))) { + plugin.log("Coop: " + player.getName() + " cooped " + user.getName() + " to island in " + + itic.getWorld().getName()); + this.itc.getCoopCommand().execute(user, itic.getLabel(), List.of(player.getName())); + } else { + plugin.log( + "Coop failed: " + player.getName() + "'s coop to " + user.getName() + " failed for island in " + + itic.getWorld().getName()); + } + } else if (clickType.equals(ClickType.SHIFT_LEFT)) { + user.closeInventory(); + if (this.itc.getTrustCommand().canExecute(user, itic.getLabel(), List.of(player.getName()))) { + plugin.log("Trust: " + player.getName() + " trusted " + user.getName() + " to island in " + + itic.getWorld().getName()); + this.itc.getTrustCommand().execute(user, itic.getLabel(), List.of(player.getName())); + } else { + plugin.log("Trust failed: " + player.getName() + "'s trust failed for " + user.getName() + + " for island in " + + itic.getWorld().getName()); + } + } + return true; + } + + private PanelItem getBlankBackground() { + return new PanelItemBuilder() + .icon(Objects.requireNonNullElse(background.icon(), new ItemStack(Material.BARRIER))) + .name((Objects.requireNonNullElse(background.title(), ""))).build(); + } + + class InviteNamePrompt extends StringPrompt { + + @Override + @NonNull + public String getPromptText(@NonNull ConversationContext context) { + return user.getTranslation("commands.island.team.invite.gui.enter-name"); + } + + @Override + public Prompt acceptInput(@NonNull ConversationContext context, String input) { + // TODO remove this and pass the options back to the GUI + if (itic.canExecute(user, itic.getLabel(), List.of(input))) { + if (itic.execute(user, itic.getLabel(), List.of(input))) { + return Prompt.END_OF_CONVERSATION; + } + } + // Set the search item to what was entered + searchName = input; + // Return to the GUI but give a second for the error to show + // TODO: return the failed input and display the options in the GUI. + Bukkit.getScheduler().runTaskLater(BentoBox.getInstance(), () -> build(user), 20L); + return Prompt.END_OF_CONVERSATION; + } + + } +} From e2a4233f693f0181b468d584fc0a88f049a4a3b7 Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 14 Mar 2024 21:11:22 -0700 Subject: [PATCH 15/24] Fix for IslandPromoteCommandTest --- .../api/commands/island/team/IslandTeamPromoteCommandTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommandTest.java index 6c58c9dbd..c3dcb2f2e 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamPromoteCommandTest.java @@ -102,6 +102,7 @@ public class IslandTeamPromoteCommandTest extends RanksManagerBeforeClassTest { when(pm.getUser("target")).thenReturn(target); when(target.getName()).thenReturn("target"); when(target.getDisplayName()).thenReturn("Target"); + when(target.getUniqueId()).thenReturn(uuid); // Managers when(plugin.getIslands()).thenReturn(im); From 0c4a4ba86217609a9b9521915ab0cbc31a62181d Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 15 Mar 2024 07:50:50 -0700 Subject: [PATCH 16/24] Fix tests. Added coops and trusted to team GUI view. --- .../commands/island/team/IslandTeamGUI.java | 4 ++-- .../island/team/IslandTeamInviteGUI.java | 19 ++++++++++--------- .../team/IslandTeamInviteCommandTest.java | 1 + 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java index f60ef8d5a..93e096a67 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java @@ -289,8 +289,8 @@ public class IslandTeamGUI { if (island == null) { return this.getBlankBackground(); } - - Optional opMember = island.getMemberSet().stream().map(User::getInstance) + int minimumRank = RanksManager.getInstance().getRankUpValue(RanksManager.VISITOR_RANK); // Get the rank above Visitor. + Optional opMember = island.getMemberSet(minimumRank).stream().map(User::getInstance) .filter((User usr) -> rankView == RanksManager.OWNER_RANK || island.getRank(usr) == rankView) // If rankView is owner then show all ranks .sorted(Comparator.comparingInt((User usr) -> island.getRank(usr)).reversed()) // Show owner on left, then descending ranks .skip(slot.slot()) // Get the head for this slot diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java index dffa8ed26..c3e2967cb 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java @@ -118,7 +118,8 @@ public class IslandTeamInviteGUI { private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { checkTemplate(template); - long count = itic.getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player)) + System.out.println(itc); + long count = itc.getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player)) .filter(player -> !player.equals(user.getPlayer())).count(); if (count > page * PER_PAGE) { // We need to show a next button @@ -172,14 +173,14 @@ public class IslandTeamInviteGUI { */ private PanelItem createProspectButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { // Player issuing the command must have an island - Island island = plugin.getIslands().getPrimaryIsland(itic.getWorld(), user.getUniqueId()); + Island island = plugin.getIslands().getPrimaryIsland(itc.getWorld(), user.getUniqueId()); if (island == null) { return this.getBlankBackground(); } if (page < 0) { page = 0; } - return itic.getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player)) + return itc.getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player)) .filter(player -> this.searchName.isBlank() ? true : player.getName().toLowerCase().contains(searchName.toLowerCase())) .filter(player -> !player.equals(user.getPlayer())).skip(slot.slot() + page * PER_PAGE).findFirst() @@ -213,33 +214,33 @@ public class IslandTeamInviteGUI { user.closeInventory(); if (itic.canExecute(user, itic.getLabel(), List.of(player.getName()))) { plugin.log("Invite sent to: " + player.getName() + " by " + user.getName() + " to join island in " - + itic.getWorld().getName()); + + itc.getWorld().getName()); itic.execute(user, itic.getLabel(), List.of(player.getName())); } else { plugin.log("Invite failed: " + player.getName() + " by " + user.getName() + " to join island in " - + itic.getWorld().getName()); + + itc.getWorld().getName()); } } else if (clickType.equals(ClickType.RIGHT)) { user.closeInventory(); if (this.itc.getCoopCommand().canExecute(user, itic.getLabel(), List.of(player.getName()))) { plugin.log("Coop: " + player.getName() + " cooped " + user.getName() + " to island in " - + itic.getWorld().getName()); + + itc.getWorld().getName()); this.itc.getCoopCommand().execute(user, itic.getLabel(), List.of(player.getName())); } else { plugin.log( "Coop failed: " + player.getName() + "'s coop to " + user.getName() + " failed for island in " - + itic.getWorld().getName()); + + itc.getWorld().getName()); } } else if (clickType.equals(ClickType.SHIFT_LEFT)) { user.closeInventory(); if (this.itc.getTrustCommand().canExecute(user, itic.getLabel(), List.of(player.getName()))) { plugin.log("Trust: " + player.getName() + " trusted " + user.getName() + " to island in " - + itic.getWorld().getName()); + + itc.getWorld().getName()); this.itc.getTrustCommand().execute(user, itic.getLabel(), List.of(player.getName())); } else { plugin.log("Trust failed: " + player.getName() + "'s trust failed for " + user.getName() + " for island in " - + itic.getWorld().getName()); + + itc.getWorld().getName()); } } return true; diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java index 706a56cb7..6bf25e07b 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java @@ -132,6 +132,7 @@ public class IslandTeamInviteCommandTest extends RanksManagerBeforeClassTest { // Parent command has no aliases when(ic.getSubCommandAliases()).thenReturn(new HashMap<>()); when(ic.getWorld()).thenReturn(world); + when(ic.getPlugin()).thenReturn(plugin); // Island islandUUID = UUID.randomUUID(); From e4e92b9634cb42e4e836a96935b00d93749b07be Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 15 Mar 2024 18:45:55 -0700 Subject: [PATCH 17/24] Fix code smell --- .../api/commands/admin/team/AdminTeamDisbandCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java index d47de209d..02c3431cc 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java @@ -82,7 +82,7 @@ public class AdminTeamDisbandCommand extends CompositeCommand { private Map getIslandsXYZ(UUID target) { return getIslands().getOwnedIslands(getWorld(), target).stream().filter(is -> is.getMemberSet().size() > 1) // Filter for teams - .collect(Collectors.toMap(island -> Util.xyz(island.getCenter().toVector()), island -> island)); + .collect(Collectors.toMap(is -> Util.xyz(island.getCenter().toVector()), is -> is)); } @Override From 06ca7a311a88a5897b6651377e9195c0e111cce6 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 15 Mar 2024 18:46:07 -0700 Subject: [PATCH 18/24] Simplify code. Fix code smells. --- .../commands/island/team/IslandTeamGUI.java | 291 ++++++++++-------- .../island/team/IslandTeamInviteGUI.java | 39 ++- 2 files changed, 184 insertions(+), 146 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java index 93e096a67..598e940a9 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamGUI.java @@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.commands.island.team.Invite.Type; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.panels.Panel; import world.bentobox.bentobox.api.panels.PanelItem; @@ -44,6 +45,10 @@ public class IslandTeamGUI { private static final List RANKS = List.of(RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK, RanksManager.MEMBER_RANK, RanksManager.TRUSTED_RANK, RanksManager.COOP_RANK); + private static final String NAME = ".name"; + + private static final String TIPS = "commands.island.team.gui.tips."; + private final User user; private final Island island; @@ -111,7 +116,7 @@ public class IslandTeamGUI { builder.name(user.getTranslation("commands.island.team.gui.buttons.invite.name")); builder.description(user.getTranslation("commands.island.team.gui.buttons.invite.description")); builder.clickHandler((panel, user, clickType, clickSlot) -> { - if (!template.actions().stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { + if (template.actions().stream().noneMatch(ar -> clickType.equals(ar.clickType()))) { // If the click type is not in the template, don't do anything return true; } @@ -133,25 +138,15 @@ public class IslandTeamGUI { builder.name(user.getTranslation("commands.island.team.gui.buttons.rank-filter.name")); builder.icon(Material.AMETHYST_SHARD); // Create description - RanksManager.getInstance().getRanks().forEach((reference, score) -> { - if (rankView == RanksManager.OWNER_RANK && score > RanksManager.VISITOR_RANK - && score <= RanksManager.OWNER_RANK) { - builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank") - + user.getTranslation(reference)); - } else if (score > RanksManager.VISITOR_RANK && score < rankView) { - builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank") - + user.getTranslation(reference)); - } else if (score <= RanksManager.OWNER_RANK && score > rankView) { - builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank") - + user.getTranslation(reference)); - } else if (score == rankView) { - builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank") - + user.getTranslation(reference)); - } - }); - builder.description(user.getTranslation("commands.island.team.gui.buttons.rank-filter.description")); + createDescription(builder); + createClickHandler(builder, template.actions()); + + return builder.build(); + } + + private void createClickHandler(PanelItemBuilder builder, @NonNull List actions) { builder.clickHandler((panel, user, clickType, clickSlot) -> { - if (!template.actions().stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { + if (actions.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) { // If the click type is not in the template, don't do anything return true; } @@ -179,7 +174,28 @@ public class IslandTeamGUI { return true; }); - return builder.build(); + + } + + private void createDescription(PanelItemBuilder builder) { + RanksManager.getInstance().getRanks().forEach((reference, score) -> { + if (rankView == RanksManager.OWNER_RANK && score > RanksManager.VISITOR_RANK + && score <= RanksManager.OWNER_RANK) { + builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank") + + user.getTranslation(reference)); + } else if (score > RanksManager.VISITOR_RANK && score < rankView) { + builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank") + + user.getTranslation(reference)); + } else if (score <= RanksManager.OWNER_RANK && score > rankView) { + builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank") + + user.getTranslation(reference)); + } else if (score == rankView) { + builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank") + + user.getTranslation(reference)); + } + }); + builder.description(user.getTranslation("commands.island.team.gui.buttons.rank-filter.description")); + } /** @@ -193,59 +209,70 @@ public class IslandTeamGUI { PanelItemBuilder builder = new PanelItemBuilder(); if (parent.isInvited(user.getUniqueId()) && user.hasPermission(parent.getAcceptCommand().getPermission())) { Invite invite = parent.getInvite(user.getUniqueId()); + if (invite == null) { + return this.getBlankBorder(); + } User inviter = User.getInstance(invite.getInviter()); String name = inviter.getName(); builder.icon(inviter.getName()); builder.name(user.getTranslation("commands.island.team.gui.buttons.invitation")); - builder.description(switch (invite.getType()) { - case COOP -> - List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.coop", TextVariables.NAME, - name)); - case TRUST -> - List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.trust", - TextVariables.NAME, name)); - default -> - List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you", TextVariables.NAME, - name), user.getTranslation("commands.island.team.invite.accept.confirmation")); - }); - // Add all the tool tips - builder.description(template.actions().stream() - .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") - + " " - + user.getTranslation(ar.tooltip())) - .toList()); - builder.clickHandler((panel, user, clickType, clickSlot) -> { - if (!template.actions().stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { - // If the click type is not in the template, don't do anything - return true; - } - if (clickType.equals(ClickType.SHIFT_LEFT) - && user.hasPermission(parent.getAcceptCommand().getPermission())) { - plugin.log("Invite accepted: " + user.getName() + " accepted " + invite.getType()); - // Accept - switch (invite.getType()) { - case COOP -> parent.getAcceptCommand().acceptCoopInvite(user, invite); - case TRUST -> parent.getAcceptCommand().acceptTrustInvite(user, invite); - default -> parent.getAcceptCommand().acceptTeamInvite(user, invite); - } - user.closeInventory(); - } - if (clickType.equals(ClickType.SHIFT_RIGHT) - && user.hasPermission(parent.getRejectCommand().getPermission())) { - // Reject - plugin.log("Invite rejected: " + user.getName() + " rejected " + invite.getType() - + " invite."); - parent.getRejectCommand().execute(user, "", List.of()); - user.closeInventory(); - } - return true; - }); + createInviteDescription(builder, invite.getType(), name, template.actions()); + createInviteClickHandler(builder, invite, template.actions()); } else { return this.getBlankBorder(); } return builder.build(); } + private void createInviteClickHandler(PanelItemBuilder builder, Invite invite, @NonNull List list) { + Type type = invite.getType(); + builder.clickHandler((panel, user, clickType, clickSlot) -> { + if (list.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) { + // If the click type is not in the template, don't do anything + return true; + } + if (clickType.equals(ClickType.SHIFT_LEFT) + && user.hasPermission(parent.getAcceptCommand().getPermission())) { + plugin.log("Invite accepted: " + user.getName() + " accepted " + type); + // Accept + switch (type) { + case COOP -> parent.getAcceptCommand().acceptCoopInvite(user, invite); + case TRUST -> parent.getAcceptCommand().acceptTrustInvite(user, invite); + default -> parent.getAcceptCommand().acceptTeamInvite(user, invite); + } + user.closeInventory(); + } + if (clickType.equals(ClickType.SHIFT_RIGHT) + && user.hasPermission(parent.getRejectCommand().getPermission())) { + // Reject + plugin.log("Invite rejected: " + user.getName() + " rejected " + type + " invite."); + parent.getRejectCommand().execute(user, "", List.of()); + user.closeInventory(); + } + return true; + }); + + } + + private void createInviteDescription(PanelItemBuilder builder, Type type, String name, + @NonNull List list) { + builder.description(switch (type) { + case COOP -> List.of( + user.getTranslation("commands.island.team.invite.name-has-invited-you.coop", TextVariables.NAME, name)); + case TRUST -> List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.trust", + TextVariables.NAME, name)); + default -> + List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you", TextVariables.NAME, name), + user.getTranslation("commands.island.team.invite.accept.confirmation")); + }); + // Add all the tool tips + builder.description(list.stream() + .map(ar -> user.getTranslation(TIPS + ar.clickType().name() + NAME) + " " + + user.getTranslation(ar.tooltip())) + .toList()); + + } + /** * Create status button panel item. * @@ -256,8 +283,8 @@ public class IslandTeamGUI { private PanelItem createStatusButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { PanelItemBuilder builder = new PanelItemBuilder(); // Player issuing the command must have an island - Island island = plugin.getIslands().getPrimaryIsland(parent.getWorld(), user.getUniqueId()); - if (island == null) { + Island is = plugin.getIslands().getPrimaryIsland(parent.getWorld(), user.getUniqueId()); + if (is == null) { return getBlankBorder(); } @@ -285,32 +312,32 @@ public class IslandTeamGUI { */ private PanelItem createMemberButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { // Player issuing the command must have an island - Island island = plugin.getIslands().getPrimaryIsland(parent.getWorld(), user.getUniqueId()); - if (island == null) { + Island is = plugin.getIslands().getPrimaryIsland(parent.getWorld(), user.getUniqueId()); + if (is == null) { return this.getBlankBackground(); } int minimumRank = RanksManager.getInstance().getRankUpValue(RanksManager.VISITOR_RANK); // Get the rank above Visitor. - Optional opMember = island.getMemberSet(minimumRank).stream().map(User::getInstance) - .filter((User usr) -> rankView == RanksManager.OWNER_RANK || island.getRank(usr) == rankView) // If rankView is owner then show all ranks - .sorted(Comparator.comparingInt((User usr) -> island.getRank(usr)).reversed()) // Show owner on left, then descending ranks + Optional opMember = is.getMemberSet(minimumRank).stream().map(User::getInstance) + .filter((User usr) -> rankView == RanksManager.OWNER_RANK || is.getRank(usr) == rankView) // If rankView is owner then show all ranks + .sorted(Comparator.comparingInt((User usr) -> is.getRank(usr)).reversed()) // Show owner on left, then descending ranks .skip(slot.slot()) // Get the head for this slot .limit(1L).findFirst(); // Get just one head if (opMember.isEmpty()) { return this.getBlankBackground(); } User member = opMember.get(); - int rank = island.getRank(member); + int rank = is.getRank(member); String rankRef = RanksManager.getInstance().getRank(rank); @NonNull List actions = template.actions(); // Make button description depending on viewer List desc = new ArrayList<>(); - int userRank = Objects.requireNonNull(island).getRank(user); + int userRank = Objects.requireNonNull(is).getRank(user); // Add the tooltip for kicking if (user.hasPermission(parent.getKickCommand().getPermission()) - && userRank >= island.getRankCommand(parent.getLabel() + " kick") && !user.equals(member)) { + && userRank >= is.getRankCommand(parent.getLabel() + " kick") && !user.equals(member)) { actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("kick")) - .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") + .map(ar -> user.getTranslation(TIPS + ar.clickType().name() + NAME) + " " + user.getTranslation(ar.tooltip())) .findFirst().ifPresent(desc::add); } @@ -319,7 +346,7 @@ public class IslandTeamGUI { && userRank >= RanksManager.OWNER_RANK && rank >= RanksManager.MEMBER_RANK) { // Add the tooltip for setowner actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("setowner")) - .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") + .map(ar -> user.getTranslation(TIPS + ar.clickType().name() + NAME) + " " + user.getTranslation(ar.tooltip())) .findFirst().ifPresent(desc::add); } @@ -328,7 +355,7 @@ public class IslandTeamGUI { && userRank < RanksManager.OWNER_RANK) { // Add the tooltip for leave actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("leave")) - .map(ar -> user.getTranslation("commands.island.team.gui.tips." + ar.clickType().name() + ".name") + .map(ar -> user.getTranslation(TIPS + ar.clickType().name() + NAME) + " " + user.getTranslation(ar.tooltip())) .findFirst().ifPresent(desc::add); } @@ -342,7 +369,7 @@ public class IslandTeamGUI { // Offline player desc.add(0, user.getTranslation(rankRef)); return new PanelItemBuilder().icon(member.getName()) - .name(offlinePlayerStatus(user, Bukkit.getOfflinePlayer(member.getUniqueId()))).description(desc) + .name(offlinePlayerStatus(Bukkit.getOfflinePlayer(member.getUniqueId()))).description(desc) .clickHandler( (panel, user, clickType, i) -> clickListener(panel, user, clickType, i, member, actions)) .build(); @@ -361,7 +388,7 @@ public class IslandTeamGUI { */ private boolean clickListener(Panel panel, User clickingUser, ClickType clickType, int i, User target, List actions) { - if (!actions.stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { + if (actions.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) { // If the click type is not in the template, don't do anything return true; } @@ -369,52 +396,11 @@ public class IslandTeamGUI { for (ItemTemplateRecord.ActionRecords action : actions) { if (clickType.equals(action.clickType())) { switch (action.actionType().toUpperCase(Locale.ENGLISH)) { - case "KICK" -> { - // Kick the player, or uncoop, or untrust - if (clickingUser.hasPermission(parent.getKickCommand().getPermission()) - && !target.equals(clickingUser) - && rank >= island.getRankCommand(parent.getLabel() + " kick")) { - plugin.log("Kick: " + clickingUser.getName() + " kicked " + target.getName() - + " from island at " + island.getCenter()); - clickingUser.closeInventory(); - if (removePlayer(clickingUser, target)) { - clickingUser.getPlayer().playSound(clickingUser.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, - 1F); - plugin.log("Kick: success"); - } else { - plugin.log("Kick: failed"); - } - } - } - case "SETOWNER" -> { - // Make the player the leader of the island - if (clickingUser.hasPermission(parent.getSetOwnerCommand().getPermission()) - && !target.equals(clickingUser) - && clickingUser.getUniqueId().equals(island.getOwner()) - && island.getRank(target) >= RanksManager.MEMBER_RANK) { - plugin.log("Set Owner: " + clickingUser.getName() + " trying to make " + target.getName() - + " owner of island at " + island.getCenter()); - clickingUser.closeInventory(); - if (parent.getSetOwnerCommand().setOwner(clickingUser, target.getUniqueId())) { - plugin.log("Set Owner: success"); - } else { - plugin.log("Set Owner: failed"); - } - } - } - case "LEAVE" -> { - if (clickingUser.hasPermission(parent.getLeaveCommand().getPermission()) - && target.equals(clickingUser) - && !clickingUser.getUniqueId().equals(island.getOwner())) { - plugin.log("Leave: " + clickingUser.getName() + " trying to leave island at " - + island.getCenter()); - clickingUser.closeInventory(); - if (parent.getLeaveCommand().leave(clickingUser)) { - plugin.log("Leave: success"); - } else { - plugin.log("Leave: failed"); - } - } + case "KICK" -> kickPlayer(clickingUser, target, rank); + case "SETOWNER" -> setOwner(clickingUser, target); + case "LEAVE" -> leave(clickingUser, target); + default -> { + // Do nothing } } } @@ -422,6 +408,51 @@ public class IslandTeamGUI { return true; } + private void leave(User clickingUser, User target) { + if (clickingUser.hasPermission(parent.getLeaveCommand().getPermission()) && target.equals(clickingUser) + && !clickingUser.getUniqueId().equals(island.getOwner())) { + plugin.log("Leave: " + clickingUser.getName() + " trying to leave island at " + island.getCenter()); + clickingUser.closeInventory(); + if (parent.getLeaveCommand().leave(clickingUser)) { + plugin.log("Leave: success"); + } else { + plugin.log("Leave: failed"); + } + } + } + + private void setOwner(User clickingUser, User target) { + // Make the player the leader of the island + if (clickingUser.hasPermission(parent.getSetOwnerCommand().getPermission()) && !target.equals(clickingUser) + && clickingUser.getUniqueId().equals(island.getOwner()) + && island.getRank(target) >= RanksManager.MEMBER_RANK) { + plugin.log("Set Owner: " + clickingUser.getName() + " trying to make " + target.getName() + + " owner of island at " + island.getCenter()); + clickingUser.closeInventory(); + if (parent.getSetOwnerCommand().setOwner(clickingUser, target.getUniqueId())) { + plugin.log("Set Owner: success"); + } else { + plugin.log("Set Owner: failed"); + } + } + } + + private void kickPlayer(User clickingUser, User target, int rank) { + // Kick the player, or uncoop, or untrust + if (clickingUser.hasPermission(parent.getKickCommand().getPermission()) && !target.equals(clickingUser) + && rank >= island.getRankCommand(parent.getLabel() + " kick")) { + plugin.log("Kick: " + clickingUser.getName() + " kicked " + target.getName() + " from island at " + + island.getCenter()); + clickingUser.closeInventory(); + if (removePlayer(clickingUser, target)) { + clickingUser.getPlayer().playSound(clickingUser.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F); + plugin.log("Kick: success"); + } else { + plugin.log("Kick: failed"); + } + } + } + private boolean removePlayer(User clicker, User member) { // If member then kick, if coop, uncoop, if trusted, then untrust return switch (island.getRank(member)) { @@ -468,28 +499,28 @@ public class IslandTeamGUI { user.getTranslation(RanksManager.getInstance().getRank(rank)), TextVariables.NUMBER, String.valueOf(island.getMemberSet(rank, false).size()))); } - message.addAll(displayOnOffline(user, rank, island, onlineMembers)); + message.addAll(displayOnOffline(rank, island, onlineMembers)); } } return message; } - private List displayOnOffline(User user, int rank, Island island, List onlineMembers) { + private List displayOnOffline(int rank, Island island, List onlineMembers) { List message = new ArrayList<>(); for (UUID member : island.getMemberSet(rank, false)) { - message.add(getMemberStatus(user, member, onlineMembers.contains(member))); + message.add(getMemberStatus(member, onlineMembers.contains(member))); } return message; } - private String getMemberStatus(User user2, UUID member, boolean online) { + private String getMemberStatus(UUID member, boolean online) { OfflinePlayer offlineMember = Bukkit.getOfflinePlayer(member); if (online) { return user.getTranslation("commands.island.team.info.member-layout.online", TextVariables.NAME, offlineMember.getName()); } else { - return offlinePlayerStatus(user, offlineMember); + return offlinePlayerStatus(offlineMember); } } @@ -499,7 +530,7 @@ public class IslandTeamGUI { * @param offlineMember member of the team * @return string */ - private String offlinePlayerStatus(User user2, OfflinePlayer offlineMember) { + private String offlinePlayerStatus(OfflinePlayer offlineMember) { String lastSeen = lastSeen(offlineMember); if (island.getMemberSet(RanksManager.MEMBER_RANK, true).contains(offlineMember.getUniqueId())) { return user.getTranslation("commands.island.team.info.member-layout.offline", TextVariables.NAME, diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java index c3e2967cb..7cf35345f 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteGUI.java @@ -3,6 +3,8 @@ package world.bentobox.bentobox.api.commands.island.team; import java.io.File; import java.util.List; import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -118,7 +120,6 @@ public class IslandTeamInviteGUI { private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { checkTemplate(template); - System.out.println(itc); long count = itc.getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player)) .filter(player -> !player.equals(user.getPlayer())).count(); if (count > page * PER_PAGE) { @@ -173,18 +174,27 @@ public class IslandTeamInviteGUI { */ private PanelItem createProspectButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { // Player issuing the command must have an island - Island island = plugin.getIslands().getPrimaryIsland(itc.getWorld(), user.getUniqueId()); - if (island == null) { + Island is = plugin.getIslands().getPrimaryIsland(itc.getWorld(), user.getUniqueId()); + if (is == null) { return this.getBlankBackground(); } if (page < 0) { page = 0; } - return itc.getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player)) - .filter(player -> this.searchName.isBlank() ? true - : player.getName().toLowerCase().contains(searchName.toLowerCase())) - .filter(player -> !player.equals(user.getPlayer())).skip(slot.slot() + page * PER_PAGE).findFirst() - .map(player -> getProspect(player, template)).orElse(this.getBlankBackground()); + // Stream of all players that the user can see + Stream visiblePlayers = itc.getWorld().getPlayers().stream().filter(user.getPlayer()::canSee); + + // Filter players based on searchName if it's not blank, and ensure they're not the user + Stream filteredPlayers = visiblePlayers + .filter(player -> this.searchName.isBlank() + || player.getName().toLowerCase().contains(searchName.toLowerCase())) + .filter(player -> !player.equals(user.getPlayer())); + + // Skipping to the correct pagination slot, then finding the first player + Optional playerOptional = filteredPlayers.skip(slot.slot() + page * PER_PAGE).findFirst(); + + // Map the player to a prospect or return a blank background if not found + return playerOptional.map(player -> getProspect(player, template)).orElse(this.getBlankBackground()); } private PanelItem getProspect(Player player, ItemTemplateRecord template) { @@ -199,14 +209,13 @@ public class IslandTeamInviteGUI { + " " + user.getTranslation(ar.tooltip())).toList(); return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName()).description(desc) .clickHandler( - (panel, user, clickType, clickSlot) -> clickHandler(panel, user, clickType, clickSlot, player, + (panel, user, clickType, clickSlot) -> clickHandler(user, clickType, player, template.actions())) .build(); } - private boolean clickHandler(Panel panel, User user, ClickType clickType, int clickSlot, Player player, - @NonNull List list) { - if (!list.stream().anyMatch(ar -> clickType.equals(ar.clickType()))) { + private boolean clickHandler(User user, ClickType clickType, Player player, @NonNull List list) { + if (list.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) { // If the click type is not in the template, don't do anything return true; } @@ -262,12 +271,10 @@ public class IslandTeamInviteGUI { @Override public Prompt acceptInput(@NonNull ConversationContext context, String input) { - // TODO remove this and pass the options back to the GUI - if (itic.canExecute(user, itic.getLabel(), List.of(input))) { - if (itic.execute(user, itic.getLabel(), List.of(input))) { + if (itic.canExecute(user, itic.getLabel(), List.of(input)) + && itic.execute(user, itic.getLabel(), List.of(input))) { return Prompt.END_OF_CONVERSATION; } - } // Set the search item to what was entered searchName = input; // Return to the GUI but give a second for the error to show From 91998b4e24948b49b1de0026d24cde2092ec7871 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 15 Mar 2024 18:47:14 -0700 Subject: [PATCH 19/24] Use method reference --- .../world/bentobox/bentobox/blueprints/BlueprintClipboard.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java index 7a38e76f1..d189f2df0 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintClipboard.java @@ -330,7 +330,7 @@ public class BlueprintClipboard { } mmh.filter(mm -> mm.isMythicMob(entity)).map(mm -> mm.getMythicMob(entity)) - .ifPresent(mmr -> bpe.setMythicMobsRecord(mmr)); + .ifPresent(bpe::setMythicMobsRecord); bpEnts.add(bpe); } From 6127cdced1894c533064b7036cce49b19765d0db Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 15 Mar 2024 18:49:20 -0700 Subject: [PATCH 20/24] Reduce complexity. --- .../bentobox/blueprints/BlueprintPaster.java | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java index 6ea748515..00a067a97 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/BlueprintPaster.java @@ -237,25 +237,7 @@ public class BlueprintPaster { private void pasteBlocks(Bits bits, int count, Optional owner, int pasteSpeed, boolean useNMS) { 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 = useNMS ? paster.pasteBlocks(island, world, blockMap) - : fallback.pasteBlocks(island, world, blockMap); - } + pasteBlocksNow(it, count, pasteSpeed, useNMS); } else { if (pasteState.equals(PasteState.BLOCKS)) { // Blocks done @@ -272,6 +254,29 @@ public class BlueprintPaster { } + private void pasteBlocksNow(Iterator> it, int count, int pasteSpeed, boolean useNMS) { + 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 = useNMS ? paster.pasteBlocks(island, world, blockMap) + : fallback.pasteBlocks(island, world, blockMap); + } + + } + private void loadChunk() { long timer = System.currentTimeMillis(); pasteState = PasteState.CHUNK_LOADING; From 5ad2ba1cd94c182905f75e39160577ec7c669082 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 15 Mar 2024 18:52:24 -0700 Subject: [PATCH 21/24] Remove useless eq()'s --- .../bentobox/managers/island/NewIslandTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java b/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java index f0d17765d..499330554 100644 --- a/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java @@ -268,7 +268,7 @@ public class NewIslandTest { verify(builder, times(2)).build(); verify(bpb).getUniqueId(); verify(ice).getBlueprintBundle(); - verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); + verify(pm).setDeaths(world, uuid, 0); verify(im, never()).setHomeLocation(eq(user), any()); } @@ -287,7 +287,7 @@ public class NewIslandTest { verify(builder, times(2)).build(); verify(bpb).getUniqueId(); verify(ice).getBlueprintBundle(); - verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); + verify(pm).setDeaths(world, uuid, 0); verify(im, never()).setHomeLocation(eq(user), any()); } @@ -305,7 +305,7 @@ public class NewIslandTest { verify(builder, times(2)).build(); verify(bpb).getUniqueId(); verify(ice).getBlueprintBundle(); - verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); + verify(pm).setDeaths(world, uuid, 0); verify(im, never()).setHomeLocation(eq(user), any()); verify(island).setProtectionRange(eq(20)); verify(island).setReserved(eq(false)); @@ -326,7 +326,7 @@ public class NewIslandTest { verify(builder, times(2)).build(); verify(bpb).getUniqueId(); verify(ice).getBlueprintBundle(); - verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); + verify(pm).setDeaths(world, uuid, 0); verify(im, never()).setHomeLocation(eq(user), any()); verify(island).setProtectionRange(eq(20)); //verify(plugin).logError("New island for user tastybento was not reserved!"); @@ -348,7 +348,7 @@ public class NewIslandTest { verify(builder, times(2)).build(); verify(bpb).getUniqueId(); verify(ice).getBlueprintBundle(); - verify(pm).setDeaths(eq(world), eq(uuid), eq(0)); + verify(pm).setDeaths(world, uuid, 0); verify(im, never()).setHomeLocation(eq(user), any()); verify(island).setProtectionRange(eq(20)); verify(plugin).logError("New island for user tastybento was not reserved!"); From db2b97d2fc6255eab942dd0f7bf38b2310485f84 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 15 Mar 2024 18:52:37 -0700 Subject: [PATCH 22/24] Remove ; after record definition --- .../bentobox/blueprints/dataobjects/BlueprintEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ec411cb91..bf6257d59 100644 --- a/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java +++ b/src/main/java/world/bentobox/bentobox/blueprints/dataobjects/BlueprintEntity.java @@ -25,7 +25,7 @@ import com.google.gson.annotations.Expose; public class BlueprintEntity { public record MythicMobRecord(String type, String displayName, double level, float power, String stance) { - }; + } // GSON can serialize records, but the record class needs to be know in advance. So this breaks out the record entries @Expose From 40e96b9169bba32e54564add6b54895a91b38738 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 15 Mar 2024 18:53:08 -0700 Subject: [PATCH 23/24] Remove useeless eq --- .../world/bentobox/bentobox/managers/island/NewIslandTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java b/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java index 499330554..70a723e2a 100644 --- a/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/island/NewIslandTest.java @@ -281,7 +281,7 @@ public class NewIslandTest { NewIsland.builder().addon(addon).name(NAME).player(user).reason(Reason.CREATE).build(); PowerMockito.mockStatic(Bukkit.class); // Verifications - verify(im).save(eq(island)); + verify(im).save(island); verify(island).setFlagsDefaults(); verify(bpm).paste(eq(addon), eq(island), eq(NAME), any(Runnable.class), eq(true)); verify(builder, times(2)).build(); From 75412a4674957fec42d4e7bec355c676b8048568 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 15 Mar 2024 19:24:41 -0700 Subject: [PATCH 24/24] Fix bug --- .../api/commands/admin/team/AdminTeamDisbandCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java index 02c3431cc..727eae5f3 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java @@ -82,7 +82,7 @@ public class AdminTeamDisbandCommand extends CompositeCommand { private Map getIslandsXYZ(UUID target) { return getIslands().getOwnedIslands(getWorld(), target).stream().filter(is -> is.getMemberSet().size() > 1) // Filter for teams - .collect(Collectors.toMap(is -> Util.xyz(island.getCenter().toVector()), is -> is)); + .collect(Collectors.toMap(is -> Util.xyz(is.getCenter().toVector()), is -> is)); } @Override