WIP - add GUI for teams.

This commit is contained in:
tastybento 2023-12-23 08:36:27 +09:00
parent f6e26aa5bd
commit 29a7fa551a
6 changed files with 330 additions and 76 deletions

View File

@ -1,7 +1,10 @@
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.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -9,14 +12,21 @@ import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
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.events.IslandBaseEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
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.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.RanksManager;
@ -24,12 +34,22 @@ 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<Integer> 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<UUID, Invite> inviteMap;
private User user;
private Island island;
public IslandTeamCommand(CompositeCommand parent) {
super(parent, "team");
inviteMap = new HashMap<>();
@ -57,12 +77,16 @@ public class IslandTeamCommand extends CompositeCommand {
}
new IslandTeamPromoteCommand(this, "promote");
new IslandTeamPromoteCommand(this, "demote");
// Panels
getPlugin().saveResource("panels/team_panel.yml", false);
}
@Override
public boolean execute(User user, String label, List<String> args) {
this.user = user;
// Player issuing the command must have an island
Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
if (island == null) {
user.sendMessage("general.errors.no-island");
return false;
@ -85,85 +109,216 @@ public class IslandTeamCommand extends CompositeCommand {
}
}
// Show members of island
showMembers(island, user);
showMembers().forEach(user::sendRawMessage);
build();
return true;
}
private void showMembers(Island island, User user) {
/**
* This method builds this GUI.
*/
private 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("INVITE", this::createInviteButton);
//panelBuilder.registerTypeBuilder("KICK", this::createKickButton);
// Register unknown type builder.
panelBuilder.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 builder.icon(Material.BARRIER).name(user.getTranslation("general.errors.no-island")).build();
}
return builder.icon(user.getName()).name(user.getTranslation("commands.island.team.gui.buttons.status.name"))
.description(showMembers()).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) {
PanelItemBuilder builder = new PanelItemBuilder();
// Player issuing the command must have an island
Island island = getIslands().getPrimaryIsland(getWorld(), user.getUniqueId());
if (island == null) {
return builder.icon(Material.BARRIER).name(user.getTranslation("general.errors.no-island")).build();
}
if (slot.slot() == 0 && island.getOwner() != null) {
// Owner
User owner = User.getInstance(island.getOwner());
return builder.icon(owner.getName())
.name(owner.getDisplayName())
.description(user.getTranslation("commands.island.team.info.rank-layout.owner", TextVariables.RANK,
user.getTranslation(RanksManager.OWNER_RANK_REF)))
.build();
}
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();
BentoBox.getInstance().logDebug("Slot = " + slot.slot());
if (slot.slot() > 0 && slot.slot() < subOwnerCount + 1) {
// Show sub owners
PanelItem item = getMemberButton(RanksManager.SUB_OWNER_RANK_REF, RanksManager.SUB_OWNER_RANK,
subOwnerCount, slot.slot());
if (item != null) {
return item;
}
}
if (slot.slot() > subOwnerCount && slot.slot() < subOwnerCount + memberCount + 1) {
// Show members
PanelItem item = getMemberButton(RanksManager.MEMBER_RANK_REF, RanksManager.MEMBER_RANK, memberCount,
slot.slot());
if (item != null) {
return item;
}
}
if (slot.slot() > subOwnerCount + memberCount && slot.slot() < subOwnerCount + memberCount + trustedCount + 1) {
// Show trusted
PanelItem item = getMemberButton(RanksManager.TRUSTED_RANK_REF, RanksManager.TRUSTED_RANK, trustedCount,
slot.slot());
if (item != null) {
return item;
}
}
if (slot.slot() > subOwnerCount + memberCount + trustedCount
&& slot.slot() < subOwnerCount + memberCount + trustedCount + coopCount + 1) {
// Show coops
PanelItem item = getMemberButton(RanksManager.COOP_RANK_REF, RanksManager.COOP_RANK, coopCount,
slot.slot());
if (item != null) {
return item;
}
}
return builder.icon(Material.BLACK_STAINED_GLASS_PANE).name("&b&r").build();
}
private PanelItem getMemberButton(String ref, int rank, long count, int slot) {
BentoBox.getInstance().logDebug(ref + " " + rank + " count = " + count + " slot = " + slot);
User player = island.getMemberSet(rank, false).stream().sorted().skip(slot - 1).limit(1L)
.map(User::getInstance).findFirst().orElse(null);
if (player != null) {
return new PanelItemBuilder().icon(player.getName()).name(player.getDisplayName())
.description(user.getTranslation("commands.island.team.info.rank-layout.generic",
TextVariables.RANK, user.getTranslation(ref), TextVariables.NUMBER, String.valueOf(count)))
.build();
} else {
BentoBox.getInstance().logDebug("no player found");
}
return null;
}
private List<String> showMembers() {
List<String> message = new ArrayList<>();
// Gather online members
long count = island.getMemberSet(RanksManager.MEMBER_RANK).stream()
long onlineMemberCount = island.getMemberSet(RanksManager.MEMBER_RANK).stream()
.filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName()))
.count();
// List of ranks that we will loop through
Integer[] ranks = new Integer[] { RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK,
RanksManager.MEMBER_RANK, RanksManager.TRUSTED_RANK, RanksManager.COOP_RANK };
// Show header:
user.sendMessage("commands.island.team.info.header", "[max]",
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(count));
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<UUID> onlineMembers = island.getMemberSet(RanksManager.COOP_RANK).stream()
.filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName()))
.toList();
for (int rank : ranks) {
for (int rank : RANKS) {
Set<UUID> players = island.getMemberSet(rank, false);
if (!players.isEmpty()) {
if (rank == RanksManager.OWNER_RANK) {
// Slightly special handling for the owner rank
user.sendMessage("commands.island.team.info.rank-layout.owner", TextVariables.RANK,
user.getTranslation(RanksManager.OWNER_RANK_REF));
message.add(user.getTranslation("commands.island.team.info.rank-layout.owner", TextVariables.RANK,
user.getTranslation(RanksManager.OWNER_RANK_REF)));
} else {
user.sendMessage("commands.island.team.info.rank-layout.generic", TextVariables.RANK,
message.add(user.getTranslation("commands.island.team.info.rank-layout.generic", TextVariables.RANK,
user.getTranslation(getPlugin().getRanksManager().getRank(rank)), TextVariables.NUMBER,
String.valueOf(island.getMemberSet(rank, false).size()));
String.valueOf(island.getMemberSet(rank, false).size())));
}
displayOnOffline(user, rank, island, onlineMembers);
message.addAll(displayOnOffline(user, rank, island, onlineMembers));
}
}
return message;
}
private List<String> displayOnOffline(User user, int rank, Island island, List<UUID> onlineMembers) {
List<String> 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);
}
}
private void displayOnOffline(User user, int rank, Island island, List<UUID> onlineMembers) {
for (UUID member : island.getMemberSet(rank, false)) {
OfflinePlayer offlineMember = Bukkit.getOfflinePlayer(member);
if (onlineMembers.contains(member)) {
// the player is online
user.sendMessage("commands.island.team.info.member-layout.online", TextVariables.NAME,
offlineMember.getName());
} else {
// A bit of handling for the last joined date
Instant lastJoined = Instant.ofEpochMilli(offlineMember.getLastPlayed());
Instant now = Instant.now();
private String offlinePlayerStatus(User user2, 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"));
}
if (island.getMemberSet(RanksManager.MEMBER_RANK, true).contains(member)) {
user.sendMessage("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
user.sendMessage("commands.island.team.info.member-layout.offline-not-last-seen",
TextVariables.NAME, offlineMember.getName());
}
}
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"));
}
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 boolean fireEvent(User user, Island island) {

View File

@ -3,10 +3,8 @@
// Copyright - 2021
//
package world.bentobox.bentobox.api.panels.reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -17,8 +15,6 @@ import org.bukkit.inventory.ItemStack;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecords;
/**
* This Record contains all necessary information about Item Template that can be used to craft panel item.
*
@ -31,13 +27,10 @@ import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord.ActionRecord
*
* @since 1.17.3
*/
public record ItemTemplateRecord(@Nullable ItemStack icon,
@Nullable String title,
@Nullable String description,
@NonNull List<ActionRecords> actions,
@NonNull Map<String, Object> dataMap,
@Nullable ItemTemplateRecord fallback)
{
public record ItemTemplateRecord(@Nullable ItemStack icon, @Nullable String title, @Nullable String description,
@NonNull List<ActionRecords> actions, @NonNull Map<String, Object> dataMap,
@Nullable ItemTemplateRecord fallback) {
/**
* Instantiates a new Item template record without actions and data map.
*
@ -46,39 +39,32 @@ public record ItemTemplateRecord(@Nullable ItemStack icon,
* @param description the description
* @param fallback the fallback
*/
public ItemTemplateRecord(ItemStack icon, String title, String description, ItemTemplateRecord fallback)
{
public ItemTemplateRecord(ItemStack icon, String title, String description, ItemTemplateRecord fallback) {
this(icon, title, description, new ArrayList<>(6), new HashMap<>(0), fallback);
}
/**
* This method adds given object associated with key into data map.
* @param key Key value of object.
* @param data Data that is associated with a key.
*/
public void addData(String key, Object data)
{
public void addData(String key, Object data) {
this.dataMap.put(key, data);
}
/**
* Add action to the actions list.
*
* @param actionData the action data
*/
public void addAction(ActionRecords actionData)
{
public void addAction(ActionRecords actionData) {
this.actions.add(actionData);
}
// ---------------------------------------------------------------------
// Section: Classes
// ---------------------------------------------------------------------
/**
* The Action Records holds data about each action.
*
@ -87,5 +73,6 @@ public record ItemTemplateRecord(@Nullable ItemStack icon,
* @param content the content of the action
* @param tooltip the tooltip of action
*/
public record ActionRecords(ClickType clickType, String actionType, String content, String tooltip) {}
public record ActionRecords(ClickType clickType, String actionType, String content, String tooltip) {
}
}

View File

@ -15,7 +15,6 @@ import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.reader.PanelTemplateRecord.TemplateItem;
/**
* This is template object for the panel reader. It contains data that can exist in the panel.

View File

@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.Nullable;
import com.google.common.base.Enums;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.util.ItemParser;
@ -83,6 +84,7 @@ public class TemplateReader
{
if (!panelLocation.exists())
{
BentoBox.getInstance().logError("Panel Template reader: Folder does not exist");
// Return null because folder does not exist.
return null;
}
@ -91,6 +93,7 @@ public class TemplateReader
if (!file.exists())
{
BentoBox.getInstance().logError(file.getAbsolutePath() + " does not exist for panel template");
// Return as file does not exist.
return null;
}
@ -117,6 +120,8 @@ public class TemplateReader
}
catch (IOException | InvalidConfigurationException e)
{
BentoBox.getInstance().logError("Error loading template");
BentoBox.getInstance().logStacktrace(e);
rec = null;
}
@ -133,6 +138,7 @@ public class TemplateReader
{
if (configurationSection == null)
{
BentoBox.getInstance().logError("No configuration section!");
// No data to return.
return null;
}

View File

@ -108,8 +108,7 @@ commands:
status:
description: displays the status of the purge
status: '&b [purged] &a islands purged out of &b [purgeable] &7(&b[percentage]
%&7)&a.'
%&7)&a.'
team:
description: manage teams
add:
@ -613,6 +612,15 @@ commands:
success: '&a Successfully reset your island name.'
team:
description: manage your team
gui:
titles:
team-panel: Team Management
buttons:
status:
name: Status
description: The status of the team
tips:
click-to-view: Click to view
info:
description: display detailed info about your team
member-layout:

View File

@ -0,0 +1,99 @@
# Name of panel used for indentification in the code - must be the same name as the filename.
team_panel:
# Title of the panel shown to the user. This is a reference and the reference will be translatable in the locale file
title: commands.island.team.gui.titles.team-panel
# The type of panel to show. Options are INVENTORY, HOPPER, DROPPER. INVENTORY is that standard chest inventory and
# the others refer to the inventories shown for those items.
type: INVENTORY
# The background of the panel. These items will be shown if other items are not there. STAINED_GLASS_PANEs give a good effect.
background:
icon: BLACK_STAINED_GLASS_PANE
# Each item may have text applied to it, but usually for background items, nothing is shown.
title: "&b&r" # Empty text. This is using the Bukkit chat color coding with &'s.
border:
# The border of each panel may be shown as a different item.
# It can be used to provide a contrast to items in the panel.
icon: BLACK_STAINED_GLASS_PANE
title: "&b&r" # Empty text
# This tag indicates which rows in the panel must be shown. The panel will be sized vertically accordingly. This does not include the borders.
# This can be a list and rows must be between 1 and 6, if used.
force-shown: [1, 2]
# The content section contains details of each item/button in the panel. The numbers indicate the rows and then then columns of each item.
content:
# Row number
1:
# Column number
2:
# The data section is a key-value list of data relavent for this button. It is interpreted by the code implemented the panel.
# The convention is to specify the type and the panel tab that will open if pressed. These are Enums in the code.
data:
type: STATUS
# Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different
# click-types.
actions:
# Each action has an arbitrary descriptive name to define it.
view:
# The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
click-type: UNKNOWN
# tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
tooltip: commands.island.team.gui.tips.click-to-view
2:
2: member_button
3: member_button
4: member_button
5: member_button
6: member_button
7: member_button
8: member_button
3:
2: member_button
3: member_button
4: member_button
5: member_button
6: member_button
7: member_button
8: member_button
4:
2: member_button
3: member_button
4: member_button
5: member_button
6: member_button
7: member_button
8: member_button
5:
2: member_button
3: member_button
4: member_button
5: member_button
6: member_button
7: member_button
8: member_button
6:
2: member_button
3: member_button
4: member_button
5: member_button
6: member_button
7: member_button
8: member_button
# This is where reuable buttons are defined.
reusable:
# This is the name of the button that is referenced
member_button:
# If the icon for a button is not defined, it defaults to AIR and so effectively will not be shown.
# icons are usually not defined if the icon is going to be dynamically set in the panel, e.g. in this case the material will vary
#icon: STONE
title: commands.island.team.gui.buttons.member.name
description: levcommands.island.team.gui.buttons.member.description
data:
type: MEMBER
# Actions cover what happens if the button is clicked or the mouse is moved over it. There can be multiple actions possible for different
# click-types.
actions:
# Each action has an arbitrary descriptive name to define it.
view:
# The click-type is the same as the bukkit {@link org.bukkit.event.inventory.ClickType}. UNKNOWN is the default.
click-type: RIGHT_CLICK
# tooltip is a locale reference that will be translated for the user and shown when they hover over the button.
tooltip: commands.island.team.gui.tips.right-click-to-kick