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; + } + + } +}