Merge pull request #2325 from BentoBoxWorld/develop

Release 2.2.0
This commit is contained in:
tastybento 2024-03-16 07:52:09 -07:00 committed by GitHub
commit 49d05054d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 1620 additions and 989 deletions

14
pom.xml
View File

@ -88,7 +88,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>2.1.1</build.version>
<build.version>2.2.0</build.version>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<server.jars>${project.basedir}/lib</server.jars>
@ -189,6 +189,12 @@
<id>MG-Dev Jenkins CI Maven Repository</id>
<url>https://ci.mg-dev.eu/plugin/repository/everything</url>
</repository>
<!-- For MythicMobs -->
<repository>
<id>nexus</id>
<name>Lumine Releases</name>
<url>https://mvn.lumine.io/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
@ -297,6 +303,12 @@
<version>${myworlds.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.lumine</groupId>
<artifactId>Mythic-Dist</artifactId>
<version>5.3.5</version>
<scope>provided</scope>
</dependency>
<!-- Shaded APIs -->
<dependency>
<groupId>com.github.TheBusyBiscuit</groupId>

View File

@ -27,6 +27,7 @@ import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.hooks.ItemsAdderHook;
import world.bentobox.bentobox.hooks.MultiverseCoreHook;
import world.bentobox.bentobox.hooks.MyWorldsHook;
import world.bentobox.bentobox.hooks.MythicMobsHook;
import world.bentobox.bentobox.hooks.SlimefunHook;
import world.bentobox.bentobox.hooks.VaultHook;
import world.bentobox.bentobox.hooks.placeholders.PlaceholderAPIHook;
@ -185,6 +186,9 @@ public class BentoBox extends JavaPlugin implements Listener {
final long enableStart = System.currentTimeMillis();
hooksManager.registerHook(new VaultHook());
// MythicMobs
hooksManager.registerHook(new MythicMobsHook());
hooksManager.registerHook(new PlaceholderAPIHook());
// Setup the Placeholders manager
placeholdersManager = new PlaceholdersManager(this);

View File

@ -82,7 +82,7 @@ public class AdminTeamDisbandCommand extends CompositeCommand {
private Map<String, Island> getIslandsXYZ(UUID target) {
return getIslands().getOwnedIslands(getWorld(), target).stream().filter(is -> is.getMemberSet().size() > 1) // Filter for teams
.collect(Collectors.toMap(island -> Util.xyz(island.getCenter().toVector()), island -> island));
.collect(Collectors.toMap(is -> Util.xyz(is.getCenter().toVector()), is -> is));
}
@Override

View File

@ -1,9 +1,17 @@
package world.bentobox.bentobox.api.commands.admin.team;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.events.island.IslandEvent;
import world.bentobox.bentobox.api.events.team.TeamEvent;
import world.bentobox.bentobox.api.localization.TextVariables;
@ -17,7 +25,11 @@ import world.bentobox.bentobox.util.Util;
*
* @author tastybento
*/
public class AdminTeamSetownerCommand extends CompositeCommand {
public class AdminTeamSetownerCommand extends ConfirmableCommand {
private @Nullable UUID targetUUID;
private Island island;
private @Nullable UUID previousOwnerUUID;
public AdminTeamSetownerCommand(CompositeCommand parent) {
super(parent, "setowner");
@ -28,35 +40,50 @@ public class AdminTeamSetownerCommand extends CompositeCommand {
setPermission("mod.team.setowner");
setParametersHelp("commands.admin.team.setowner.parameters");
setDescription("commands.admin.team.setowner.description");
this.setOnlyPlayer(true);
}
@Override
public boolean execute(User user, String label, List<String> args) {
public boolean canExecute(User user, String label, List<String> args) {
// If args are not right, show help
if (args.size() != 1) {
showHelp(this, user);
return false;
}
// Get target
UUID targetUUID = Util.getUUID(args.get(0));
targetUUID = Util.getUUID(args.get(0));
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
if (!getIslands().inTeam(getWorld(), targetUUID)) {
user.sendMessage("general.errors.not-in-team");
// Check that user is on an island
Optional<Island> opIsland = getIslands().getIslandAt(user.getLocation());
if (opIsland.isEmpty()) {
user.sendMessage("commands.admin.team.setowner.must-be-on-island");
return false;
}
Island island = getIslands().getPrimaryIsland(getWorld(), targetUUID);
UUID previousOwnerUUID = island.getOwner();
island = opIsland.get();
previousOwnerUUID = island.getOwner();
if (targetUUID.equals(previousOwnerUUID)) {
user.sendMessage("commands.admin.team.setowner.already-owner", TextVariables.NAME, args.get(0));
return false;
}
return true;
}
// Get the User corresponding to the current owner
public boolean execute(User user, String label, List<String> args) {
Objects.requireNonNull(island);
Objects.requireNonNull(targetUUID);
this.askConfirmation(user, user.getTranslation("commands.admin.team.setowner.confirmation", TextVariables.NAME,
args.get(0), TextVariables.XYZ, Util.xyz(island.getCenter().toVector())), () -> changeOwner(user));
return true;
}
protected void changeOwner(User user) {
User target = User.getInstance(targetUUID);
// Fire event so add-ons know
// Call the setowner event
TeamEvent.builder().island(island).reason(TeamEvent.Reason.SETOWNER).involvedPlayer(targetUUID).admin(true)
@ -70,8 +97,20 @@ public class AdminTeamSetownerCommand extends CompositeCommand {
.build();
// Make new owner
getIslands().setOwner(getWorld(), user, targetUUID);
user.sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, args.get(0));
getIslands().setOwner(user, targetUUID, island, RanksManager.MEMBER_RANK);
user.sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, target.getName());
// Report if this made player have more islands than expected
// Get how many islands this player has
int num = this.getIslands().getNumberOfConcurrentIslands(targetUUID, getWorld());
int max = target.getPermissionValue(
this.getIWM().getAddon(getWorld()).map(GameModeAddon::getPermissionPrefix).orElse("") + "island.number",
this.getIWM().getWorldSettings(getWorld()).getConcurrentIslands());
if (num > max) {
// You cannot make an island
user.sendMessage("commands.admin.team.setowner.extra-islands", TextVariables.NUMBER, String.valueOf(num),
"[max]", String.valueOf(max));
}
// Call the rank change event for the old island owner
if (previousOwnerUUID != null) {
@ -80,6 +119,13 @@ public class AdminTeamSetownerCommand extends CompositeCommand {
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(RanksManager.OWNER_RANK, island.getRank(previousOwnerUUID)).build();
}
return true;
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : "";
List<String> options = Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
return Optional.of(Util.tabLimit(options, lastArg));
}
}

View File

@ -53,7 +53,7 @@ public class IslandSethomeCommand extends ConfirmableCommand {
// Check number of homes
int maxHomes = getIslands().getIslands(getWorld(), user).stream().mapToInt(getIslands()::getMaxHomes).sum();
int maxHomes = getIslands().getMaxHomes(island);
if (getIslands().getNumberOfHomesIfAdded(island, String.join(" ", args)) > maxHomes) {
user.sendMessage("commands.island.sethome.too-many-homes", TextVariables.NUMBER, String.valueOf(maxHomes));
user.sendMessage("commands.island.sethome.homes-are");

View File

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

View File

@ -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<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;
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<String> 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,510 +121,11 @@ public class IslandTeamCommand extends CompositeCommand {
@Override
public boolean execute(User user, String label, List<String> 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<ActionRecords> actions) {
if (slot == 0 && island.getOwner() != null) {
// Owner
return getMemberButton(RanksManager.OWNER_RANK, 1, actions);
}
String ref = RanksManager.getInstance().getRank(targetRank);
Optional<User> 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<String> 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<ActionRecords> 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())) {
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.execute(user, kickCommand.getLabel(), List.of(member.getName()));
} else {
yield false;
}
}
};
}
private List<String> showMembers() {
List<String> 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<UUID> onlineMembers = island.getMemberSet(RanksManager.COOP_RANK).stream()
.filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName()))
.toList();
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
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<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);
}
}
/**
* 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();
@ -730,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;
}
}

View File

@ -0,0 +1,567 @@
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.commands.island.team.Invite.Type;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.Panel;
import world.bentobox.bentobox.api.panels.PanelItem;
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<Integer> RANKS = List.of(RanksManager.OWNER_RANK, RanksManager.SUB_OWNER_RANK,
RanksManager.MEMBER_RANK, RanksManager.TRUSTED_RANK, RanksManager.COOP_RANK);
private static final String NAME = ".name";
private static final String TIPS = "commands.island.team.gui.tips.";
private final User user;
private final Island island;
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().noneMatch(ar -> clickType.equals(ar.clickType()))) {
// If the click type is not in the template, don't do anything
return true;
}
if (clickType.equals(ClickType.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
createDescription(builder);
createClickHandler(builder, template.actions());
return builder.build();
}
private void createClickHandler(PanelItemBuilder builder, @NonNull List<ActionRecords> actions) {
builder.clickHandler((panel, user, clickType, clickSlot) -> {
if (actions.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) {
// If the click type is not in the template, don't do anything
return true;
}
if (clickType.equals(ClickType.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;
});
}
private void createDescription(PanelItemBuilder builder) {
RanksManager.getInstance().getRanks().forEach((reference, score) -> {
if (rankView == RanksManager.OWNER_RANK && score > RanksManager.VISITOR_RANK
&& score <= RanksManager.OWNER_RANK) {
builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank")
+ user.getTranslation(reference));
} else if (score > RanksManager.VISITOR_RANK && score < rankView) {
builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank")
+ user.getTranslation(reference));
} else if (score <= RanksManager.OWNER_RANK && score > rankView) {
builder.description(user.getTranslation("protection.panel.flag-item.blocked-rank")
+ user.getTranslation(reference));
} else if (score == rankView) {
builder.description(user.getTranslation("protection.panel.flag-item.allowed-rank")
+ user.getTranslation(reference));
}
});
builder.description(user.getTranslation("commands.island.team.gui.buttons.rank-filter.description"));
}
/**
* 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());
if (invite == null) {
return this.getBlankBorder();
}
User inviter = User.getInstance(invite.getInviter());
String name = inviter.getName();
builder.icon(inviter.getName());
builder.name(user.getTranslation("commands.island.team.gui.buttons.invitation"));
createInviteDescription(builder, invite.getType(), name, template.actions());
createInviteClickHandler(builder, invite, template.actions());
} else {
return this.getBlankBorder();
}
return builder.build();
}
private void createInviteClickHandler(PanelItemBuilder builder, Invite invite, @NonNull List<ActionRecords> list) {
Type type = invite.getType();
builder.clickHandler((panel, user, clickType, clickSlot) -> {
if (list.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) {
// If the click type is not in the template, don't do anything
return true;
}
if (clickType.equals(ClickType.SHIFT_LEFT)
&& user.hasPermission(parent.getAcceptCommand().getPermission())) {
plugin.log("Invite accepted: " + user.getName() + " accepted " + type);
// Accept
switch (type) {
case COOP -> parent.getAcceptCommand().acceptCoopInvite(user, invite);
case TRUST -> parent.getAcceptCommand().acceptTrustInvite(user, invite);
default -> parent.getAcceptCommand().acceptTeamInvite(user, invite);
}
user.closeInventory();
}
if (clickType.equals(ClickType.SHIFT_RIGHT)
&& user.hasPermission(parent.getRejectCommand().getPermission())) {
// Reject
plugin.log("Invite rejected: " + user.getName() + " rejected " + type + " invite.");
parent.getRejectCommand().execute(user, "", List.of());
user.closeInventory();
}
return true;
});
}
private void createInviteDescription(PanelItemBuilder builder, Type type, String name,
@NonNull List<ActionRecords> list) {
builder.description(switch (type) {
case COOP -> List.of(
user.getTranslation("commands.island.team.invite.name-has-invited-you.coop", TextVariables.NAME, name));
case TRUST -> List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you.trust",
TextVariables.NAME, name));
default ->
List.of(user.getTranslation("commands.island.team.invite.name-has-invited-you", TextVariables.NAME, name),
user.getTranslation("commands.island.team.invite.accept.confirmation"));
});
// Add all the tool tips
builder.description(list.stream()
.map(ar -> user.getTranslation(TIPS + ar.clickType().name() + NAME) + " "
+ user.getTranslation(ar.tooltip()))
.toList());
}
/**
* Create status button panel item.
*
* @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 is = plugin.getIslands().getPrimaryIsland(parent.getWorld(), user.getUniqueId());
if (is == 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 is = plugin.getIslands().getPrimaryIsland(parent.getWorld(), user.getUniqueId());
if (is == null) {
return this.getBlankBackground();
}
int minimumRank = RanksManager.getInstance().getRankUpValue(RanksManager.VISITOR_RANK); // Get the rank above Visitor.
Optional<User> opMember = is.getMemberSet(minimumRank).stream().map(User::getInstance)
.filter((User usr) -> rankView == RanksManager.OWNER_RANK || is.getRank(usr) == rankView) // If rankView is owner then show all ranks
.sorted(Comparator.comparingInt((User usr) -> is.getRank(usr)).reversed()) // Show owner on left, then descending ranks
.skip(slot.slot()) // Get the head for this slot
.limit(1L).findFirst(); // Get just one head
if (opMember.isEmpty()) {
return this.getBlankBackground();
}
User member = opMember.get();
int rank = is.getRank(member);
String rankRef = RanksManager.getInstance().getRank(rank);
@NonNull
List<ActionRecords> actions = template.actions();
// Make button description depending on viewer
List<String> desc = new ArrayList<>();
int userRank = Objects.requireNonNull(is).getRank(user);
// Add the tooltip for kicking
if (user.hasPermission(parent.getKickCommand().getPermission())
&& userRank >= is.getRankCommand(parent.getLabel() + " kick") && !user.equals(member)) {
actions.stream().filter(ar -> ar.actionType().equalsIgnoreCase("kick"))
.map(ar -> user.getTranslation(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(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(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(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<ActionRecords> actions) {
if (actions.stream().noneMatch(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" -> kickPlayer(clickingUser, target, rank);
case "SETOWNER" -> setOwner(clickingUser, target);
case "LEAVE" -> leave(clickingUser, target);
default -> {
// Do nothing
}
}
}
}
return true;
}
private void leave(User clickingUser, User target) {
if (clickingUser.hasPermission(parent.getLeaveCommand().getPermission()) && target.equals(clickingUser)
&& !clickingUser.getUniqueId().equals(island.getOwner())) {
plugin.log("Leave: " + clickingUser.getName() + " trying to leave island at " + island.getCenter());
clickingUser.closeInventory();
if (parent.getLeaveCommand().leave(clickingUser)) {
plugin.log("Leave: success");
} else {
plugin.log("Leave: failed");
}
}
}
private void setOwner(User clickingUser, User target) {
// Make the player the leader of the island
if (clickingUser.hasPermission(parent.getSetOwnerCommand().getPermission()) && !target.equals(clickingUser)
&& clickingUser.getUniqueId().equals(island.getOwner())
&& island.getRank(target) >= RanksManager.MEMBER_RANK) {
plugin.log("Set Owner: " + clickingUser.getName() + " trying to make " + target.getName()
+ " owner of island at " + island.getCenter());
clickingUser.closeInventory();
if (parent.getSetOwnerCommand().setOwner(clickingUser, target.getUniqueId())) {
plugin.log("Set Owner: success");
} else {
plugin.log("Set Owner: failed");
}
}
}
private void kickPlayer(User clickingUser, User target, int rank) {
// Kick the player, or uncoop, or untrust
if (clickingUser.hasPermission(parent.getKickCommand().getPermission()) && !target.equals(clickingUser)
&& rank >= island.getRankCommand(parent.getLabel() + " kick")) {
plugin.log("Kick: " + clickingUser.getName() + " kicked " + target.getName() + " from island at "
+ island.getCenter());
clickingUser.closeInventory();
if (removePlayer(clickingUser, target)) {
clickingUser.getPlayer().playSound(clickingUser.getLocation(), Sound.BLOCK_GLASS_BREAK, 1F, 1F);
plugin.log("Kick: success");
} else {
plugin.log("Kick: failed");
}
}
}
private boolean removePlayer(User clicker, User member) {
// If member then kick, if coop, uncoop, if trusted, then untrust
return switch (island.getRank(member)) {
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<String> showMembers() {
List<String> 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<UUID> onlineMembers = island.getMemberSet(RanksManager.COOP_RANK).stream()
.filter(uuid -> Util.getOnlinePlayerList(user).contains(Bukkit.getOfflinePlayer(uuid).getName()))
.toList();
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
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(rank, island, onlineMembers));
}
}
return message;
}
private List<String> displayOnOffline(int rank, Island island, List<UUID> onlineMembers) {
List<String> message = new ArrayList<>();
for (UUID member : island.getMemberSet(rank, false)) {
message.add(getMemberStatus(member, onlineMembers.contains(member)));
}
return message;
}
private String getMemberStatus(UUID member, boolean online) {
OfflinePlayer offlineMember = Bukkit.getOfflinePlayer(member);
if (online) {
return user.getTranslation("commands.island.team.info.member-layout.online", TextVariables.NAME,
offlineMember.getName());
} else {
return offlinePlayerStatus(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(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;
}
}

View File

@ -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<String> 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<ActionRecords> 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;
}
}

View File

@ -0,0 +1,287 @@
package world.bentobox.bentobox.api.commands.island.team;
import java.io.File;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import org.bukkit.Bukkit;
import org.bukkit.Material;
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 = itc.getWorld().getPlayers().stream().filter(player -> user.getPlayer().canSee(player))
.filter(player -> !player.equals(user.getPlayer())).count();
if (count > page * PER_PAGE) {
// We need to show a next button
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 is = plugin.getIslands().getPrimaryIsland(itc.getWorld(), user.getUniqueId());
if (is == null) {
return this.getBlankBackground();
}
if (page < 0) {
page = 0;
}
// Stream of all players that the user can see
Stream<Player> visiblePlayers = itc.getWorld().getPlayers().stream().filter(user.getPlayer()::canSee);
// Filter players based on searchName if it's not blank, and ensure they're not the user
Stream<Player> filteredPlayers = visiblePlayers
.filter(player -> this.searchName.isBlank()
|| player.getName().toLowerCase().contains(searchName.toLowerCase()))
.filter(player -> !player.equals(user.getPlayer()));
// Skipping to the correct pagination slot, then finding the first player
Optional<Player> playerOptional = filteredPlayers.skip(slot.slot() + page * PER_PAGE).findFirst();
// Map the player to a prospect or return a blank background if not found
return playerOptional.map(player -> getProspect(player, template)).orElse(this.getBlankBackground());
}
private PanelItem getProspect(Player player, ItemTemplateRecord template) {
// 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<String> 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(user, clickType, player,
template.actions()))
.build();
}
private boolean clickHandler(User user, ClickType clickType, Player player, @NonNull List<ActionRecords> list) {
if (list.stream().noneMatch(ar -> clickType.equals(ar.clickType()))) {
// If the click type is not in the template, don't do anything
return true;
}
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 "
+ itc.getWorld().getName());
itic.execute(user, itic.getLabel(), List.of(player.getName()));
} else {
plugin.log("Invite failed: " + player.getName() + " by " + user.getName() + " to join island in "
+ itc.getWorld().getName());
}
} else if (clickType.equals(ClickType.RIGHT)) {
user.closeInventory();
if (this.itc.getCoopCommand().canExecute(user, itic.getLabel(), List.of(player.getName()))) {
plugin.log("Coop: " + player.getName() + " cooped " + user.getName() + " to island in "
+ itc.getWorld().getName());
this.itc.getCoopCommand().execute(user, itic.getLabel(), List.of(player.getName()));
} else {
plugin.log(
"Coop failed: " + player.getName() + "'s coop to " + user.getName() + " failed for island in "
+ itc.getWorld().getName());
}
} else if (clickType.equals(ClickType.SHIFT_LEFT)) {
user.closeInventory();
if (this.itc.getTrustCommand().canExecute(user, itic.getLabel(), List.of(player.getName()))) {
plugin.log("Trust: " + player.getName() + " trusted " + user.getName() + " to island in "
+ itc.getWorld().getName());
this.itc.getTrustCommand().execute(user, itic.getLabel(), List.of(player.getName()));
} else {
plugin.log("Trust failed: " + player.getName() + "'s trust failed for " + user.getName()
+ " for island in "
+ itc.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) {
if (itic.canExecute(user, itic.getLabel(), List.of(input))
&& itic.execute(user, itic.getLabel(), List.of(input))) {
return Prompt.END_OF_CONVERSATION;
}
// Set the search item to what was entered
searchName = input;
// Return to the GUI but give a second for the error to show
// 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;
}
}
}

View File

@ -90,7 +90,10 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
}
}
protected void kick(User user, UUID targetUUID) {
protected boolean kick(User user, UUID targetUUID) {
if (targetUUID == null) {
return false;
}
User target = User.getInstance(targetUUID);
Island oldIsland = Objects.requireNonNull(getIslands().getIsland(getWorld(), targetUUID)); // Should never be
// null because of
@ -99,7 +102,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
IslandBaseEvent event = TeamEvent.builder().island(oldIsland).reason(TeamEvent.Reason.KICK)
.involvedPlayer(targetUUID).build();
if (event.isCancelled()) {
return;
return false;
}
target.sendMessage("commands.island.team.kick.player-kicked", TextVariables.GAMEMODE,
getAddon().getDescription().getName(), TextVariables.NAME, user.getName(), TextVariables.DISPLAY_NAME,
@ -121,6 +124,7 @@ public class IslandTeamKickCommand extends ConfirmableCommand {
getParent().getSubCommand("invite").ifPresent(c -> c.setCooldown(oldIsland.getUniqueId(),
targetUUID.toString(), getSettings().getInviteCooldown() * 60));
}
return true;
}
@Override

View File

@ -15,6 +15,9 @@ import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
/**
* Handle promotion and demotion
*/
public class IslandTeamPromoteCommand extends CompositeCommand {
private User target;
@ -45,7 +48,7 @@ public class IslandTeamPromoteCommand extends CompositeCommand {
showHelp(this, user);
return false;
}
// Check if the user has a team
if (!getIslands().inTeam(getWorld(), user.getUniqueId())) {
user.sendMessage("general.errors.no-team");
return false;
@ -65,6 +68,11 @@ public class IslandTeamPromoteCommand extends CompositeCommand {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0));
return false;
}
// Check that target is a member of this island
if (!island.getMemberSet().contains(target.getUniqueId())) {
user.sendMessage("commands.island.team.promote.errors.must-be-member");
return false;
}
// Check if the user is not trying to promote/ demote himself
if (target.equals(user)) {
if (this.getLabel().equals("promote")) {
@ -100,7 +108,8 @@ public class IslandTeamPromoteCommand extends CompositeCommand {
if (this.getLabel().equals("promote")) {
int nextRank = RanksManager.getInstance().getRankUpValue(currentRank);
// Stop short of owner
if (nextRank != RanksManager.OWNER_RANK && nextRank > currentRank) {
if (nextRank < RanksManager.OWNER_RANK && currentRank >= RanksManager.MEMBER_RANK
&& nextRank > currentRank) {
island.setRank(target, nextRank);
String rankName = user.getTranslation(RanksManager.getInstance().getRank(nextRank));
user.sendMessage("commands.island.team.promote.success", TextVariables.NAME, target.getName(), TextVariables.RANK, rankName, TextVariables.DISPLAY_NAME, target.getDisplayName());

View File

@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@ -43,6 +44,7 @@ import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.hooks.MythicMobsHook;
/**
* The clipboard provides the holding spot for an active blueprint that is being
@ -67,6 +69,7 @@ public class BlueprintClipboard {
private final Map<Vector, BlueprintBlock> bpAttachable = new LinkedHashMap<>();
private final Map<Vector, BlueprintBlock> bpBlocks = new LinkedHashMap<>();
private final BentoBox plugin = BentoBox.getInstance();
private Optional<MythicMobsHook> mmh;
/**
* Create a clipboard for blueprint
@ -74,9 +77,16 @@ public class BlueprintClipboard {
*/
public BlueprintClipboard(@NonNull Blueprint blueprint) {
this.blueprint = blueprint;
// MythicMobs
mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance)
.map(MythicMobsHook.class::cast);
}
public BlueprintClipboard() { }
public BlueprintClipboard() {
// MythicMobs
mmh = plugin.getHooks().getHook("MythicMobs").filter(MythicMobsHook.class::isInstance)
.map(MythicMobsHook.class::cast);
}
/**
* Copy the blocks between pos1 and pos2 into the clipboard for a user.
@ -285,6 +295,7 @@ public class BlueprintClipboard {
List<BlueprintEntity> bpEnts = new ArrayList<>();
for (LivingEntity entity: entities) {
BlueprintEntity bpe = new BlueprintEntity();
bpe.setType(entity.getType());
bpe.setCustomName(entity.getCustomName());
if (entity instanceof Villager villager) {
@ -317,6 +328,10 @@ public class BlueprintClipboard {
if (entity instanceof Horse horse) {
bpe.setStyle(horse.getStyle());
}
mmh.filter(mm -> mm.isMythicMob(entity)).map(mm -> mm.getMythicMob(entity))
.ifPresent(bpe::setMythicMobsRecord);
bpEnts.add(bpe);
}
return bpEnts;

View File

@ -237,25 +237,7 @@ public class BlueprintPaster {
private void pasteBlocks(Bits bits, int count, Optional<User> owner, int pasteSpeed, boolean useNMS) {
Iterator<Entry<Vector, BlueprintBlock>> it = pasteState.equals(PasteState.BLOCKS) ? bits.it : bits.it2;
if (it.hasNext()) {
Map<Location, BlueprintBlock> blockMap = new HashMap<>();
// Paste blocks
while (count < pasteSpeed) {
if (!it.hasNext()) {
break;
}
Entry<Vector, BlueprintBlock> entry = it.next();
Location pasteTo = location.clone().add(entry.getKey());
// pos1 and pos2 update
updatePos(pasteTo);
BlueprintBlock block = entry.getValue();
blockMap.put(pasteTo, block);
count++;
}
if (!blockMap.isEmpty()) {
currentTask = useNMS ? paster.pasteBlocks(island, world, blockMap)
: fallback.pasteBlocks(island, world, blockMap);
}
pasteBlocksNow(it, count, pasteSpeed, useNMS);
} else {
if (pasteState.equals(PasteState.BLOCKS)) {
// Blocks done
@ -272,6 +254,29 @@ public class BlueprintPaster {
}
private void pasteBlocksNow(Iterator<Entry<Vector, BlueprintBlock>> it, int count, int pasteSpeed, boolean useNMS) {
Map<Location, BlueprintBlock> blockMap = new HashMap<>();
// Paste blocks
while (count < pasteSpeed) {
if (!it.hasNext()) {
break;
}
Entry<Vector, BlueprintBlock> entry = it.next();
Location pasteTo = location.clone().add(entry.getKey());
// pos1 and pos2 update
updatePos(pasteTo);
BlueprintBlock block = entry.getValue();
blockMap.put(pasteTo, block);
count++;
}
if (!blockMap.isEmpty()) {
currentTask = useNMS ? paster.pasteBlocks(island, world, blockMap)
: fallback.pasteBlocks(island, world, blockMap);
}
}
private void loadChunk() {
long timer = System.currentTimeMillis();
pasteState = PasteState.CHUNK_LOADING;

View File

@ -24,6 +24,19 @@ import com.google.gson.annotations.Expose;
*/
public class BlueprintEntity {
public record MythicMobRecord(String type, String displayName, double level, float power, String stance) {
}
// GSON can serialize records, but the record class needs to be know in advance. So this breaks out the record entries
@Expose
String MMtype;
@Expose
Double MMLevel;
@Expose
String MMStance;
@Expose
Float MMpower;
@Expose
private DyeColor color;
@Expose
@ -50,7 +63,6 @@ public class BlueprintEntity {
private Integer experience;
@Expose
private Villager.Type villagerType;
/**
* @since 1.8.0
@ -85,7 +97,6 @@ public class BlueprintEntity {
if (style != null && e instanceof Horse horse) {
horse.setStyle(style);
}
}
/**
@ -270,5 +281,24 @@ public class BlueprintEntity {
public void setDomestication(Integer domestication) {
this.domestication = domestication;
}
/**
* @return the mythicMobsRecord
*/
public MythicMobRecord getMythicMobsRecord() {
return new MythicMobRecord(this.MMtype, this.getCustomName(), this.MMLevel, this.MMpower, this.MMStance);
}
/**
* @param mmr the mythicMobsRecord to set
* @since 2.1.0
*/
public void setMythicMobsRecord(MythicMobRecord mmr) {
this.setCustomName(mmr.displayName());
this.MMtype = mmr.type();
this.MMLevel = mmr.level();
this.MMStance = mmr.stance();
this.MMpower = mmr.power();
}
}

View File

@ -0,0 +1,70 @@
package world.bentobox.bentobox.hooks;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Entity;
import io.lumine.mythic.bukkit.BukkitAdapter;
import io.lumine.mythic.bukkit.MythicBukkit;
import io.lumine.mythic.core.mobs.ActiveMob;
import world.bentobox.bentobox.api.hooks.Hook;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity.MythicMobRecord;
/**
* Provides implementation and interfacing to interact with MythicMobs.
*
* @author tastybento
* @since 2.2.0
*/
public class MythicMobsHook extends Hook {
public MythicMobsHook() {
super("MythicMobs", Material.CREEPER_HEAD);
}
public boolean isMythicMob(Entity bukkitEntity) {
return MythicBukkit.inst().getMobManager().isMythicMob(bukkitEntity);
}
public MythicMobRecord getMythicMob(Entity bukkitEntity) {
ActiveMob mm = MythicBukkit.inst().getMobManager().getActiveMob(bukkitEntity.getUniqueId()).orElse(null);
if (mm != null) {
return new MythicMobRecord(mm.getMobType(), mm.getDisplayName(), mm.getLevel(),
mm.getPower(),
mm.getStance());
}
return null;
}
@Override
public boolean hook() {
return true; // The hook process shouldn't fail
}
@Override
public String getFailureCause() {
return null; // The hook process shouldn't fail
}
/**
* Spawn a MythicMob
* @param mmr MythicMobRecord
* @param spawnLocation location
* @return true if spawn is successful
*/
public boolean spawnMythicMob(MythicMobRecord mmr, Location spawnLocation) {
return MythicBukkit.inst().getMobManager().getMythicMob(mmr.type()).map(mob -> {
// A delay is required before spawning, I assume because the blocks are pasted using NMS
Bukkit.getScheduler().runTaskLater(getPlugin(), () -> {
// spawns mob
ActiveMob activeMob = mob.spawn(BukkitAdapter.adapt(spawnLocation), mmr.level());
activeMob.setDisplayName(mmr.displayName());
activeMob.setPower(mmr.power());
activeMob.setStance(mmr.stance());
}, 40L);
return true;
}).orElse(false);
}
}

View File

@ -28,7 +28,6 @@ import org.bukkit.inventory.BeaconInventory;
import org.bukkit.inventory.BrewerInventory;
import org.bukkit.inventory.CartographyInventory;
import org.bukkit.inventory.ChiseledBookshelfInventory;
import org.bukkit.inventory.CraftingInventory;
import org.bukkit.inventory.DoubleChestInventory;
import org.bukkit.inventory.EnchantingInventory;
import org.bukkit.inventory.FurnaceInventory;
@ -185,9 +184,6 @@ public class InventoryListener extends FlagListener
} else if (e.getInventory() instanceof ChiseledBookshelfInventory) {
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.BOOKSHELF);
return true;
} else if (e.getInventory() instanceof CraftingInventory) {
this.checkIsland(e, player, e.getInventory().getLocation(), Flags.CRAFTING);
return true;
} else if (e.getInventory() instanceof DoubleChestInventory) {
checkInvHolder(e.getInventory().getLocation(), e, player);
return true;

View File

@ -15,7 +15,7 @@ import world.bentobox.bentobox.lists.Flags;
/**
* Listener for {@link Flags#CROP_TRAMPLE, Flags#PRESSURE_PLATE, Flags#TURTLE_EGGS, Flags#BUTTON}
* Listener for {@link Flags#CROP_TRAMPLE}, {@link Flags#PRESSURE_PLATE}, {@link Flags#TURTLE_EGGS}, {@link Flags#BUTTON}
* @author tastybento
*
*/

View File

@ -20,6 +20,7 @@ import world.bentobox.bentobox.api.flags.FlagListener;
import world.bentobox.bentobox.lists.Flags;
/**
* Provides protection for placing blocks.
* @author tastybento
*/
public class PlaceBlocksListener extends FlagListener

View File

@ -674,7 +674,7 @@ public final class Flags {
/**
* Crop Planting
* Controls who gets to plant crops on tilled soil.
* Listener is {@link PlaceBlockListener}
* Listener is {@link world.bentobox.bentobox.listeners.flags.protection.PlaceBlocksListener}
* @since 1.23.0
*/
public static final Flag CROP_PLANTING = new Flag.Builder("CROP_PLANTING", Material.PUMPKIN_SEEDS).mode(Flag.Mode.BASIC).type(Type.PROTECTION).build();

View File

@ -214,8 +214,11 @@ public class BlueprintClipboardManager {
}
private void unzipFiles(final ZipInputStream zipInputStream, final Path unzipFilePath) throws IOException {
if (!unzipFilePath.toFile().getCanonicalPath().startsWith(blueprintFolder.getCanonicalPath())) {
throw new IOException("Entry is outside of the target directory");
// Prevent directory traversal attacks by normalizing the path
if (!unzipFilePath.startsWith(blueprintFolder.getCanonicalFile().toPath().normalize())) {
throw new IOException(
"Blueprint file is trying to write outside of the target directory! Blocked attempt to write to "
+ unzipFilePath.toString());
}
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(unzipFilePath.toFile().getCanonicalPath()))) {
byte[] bytesIn = new byte[1024];

View File

@ -341,7 +341,7 @@ public class IslandsManager {
* Gets all the islands for this player in this world that this player owns.
*
* @param world world to check
* @param uniqueId user's UUID
* @param user user
* @return List of islands or empty list if none found for user
* @since 2.1.0
*/
@ -1520,17 +1520,22 @@ public class IslandsManager {
/**
* Sets this target as the owner for this island
*
* @param user previous owner
* @param user user making the change
* @param targetUUID new owner
* @param island island to register
* @param rank rank to which to set old owner.
*/
public void setOwner(User user, UUID targetUUID, Island island, int rank) {
islandCache.setOwner(island, targetUUID);
// Set old owner as sub-owner on island.
if (rank > RanksManager.VISITOR_RANK) {
island.setRank(user, rank);
// Demote the old owner
if (rank >= RanksManager.OWNER_RANK) {
plugin.logWarning("Setowner: previous owner's rank cannot be higher than SubOwner");
rank = RanksManager.SUB_OWNER_RANK;
}
if (rank > RanksManager.VISITOR_RANK && island.getOwner() != null) {
island.setRank(island.getOwner(), rank);
}
// Make the new owner
islandCache.setOwner(island, targetUUID);
user.sendMessage("commands.island.team.setowner.name-is-the-owner", "[name]",
plugin.getPlayers().getName(targetUUID));

View File

@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
/**
@ -353,6 +354,7 @@ public class IslandCache {
if (newOwnerUUID != null) {
islandsByUUID.computeIfAbsent(newOwnerUUID, k -> new HashSet<>()).add(island);
}
island.setRank(newOwnerUUID, RanksManager.OWNER_RANK);
islandsByLocation.put(island.getCenter(), island);
islandsById.put(island.getUniqueId(), island);
}

View File

@ -33,6 +33,7 @@ import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.hooks.MythicMobsHook;
import world.bentobox.bentobox.nms.PasteHandler;
/**
@ -172,29 +173,46 @@ public class DefaultPasteUtil {
public static CompletableFuture<Void> setEntity(Island island, Location location, List<BlueprintEntity> list) {
World world = location.getWorld();
assert world != null;
return Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null).forEach(k -> {
LivingEntity e = (LivingEntity) location.getWorld().spawnEntity(location, k.getType());
if (k.getCustomName() != null) {
String customName = k.getCustomName();
return Util.getChunkAtAsync(location).thenRun(() -> list.stream().filter(k -> k.getType() != null)
.forEach(k -> spawnBlueprintEntity(k, location, island)));
}
if (island != null) {
// Parse any placeholders in the entity's name, if the owner's connected (he should)
Optional<Player> owner = Optional.ofNullable(island.getOwner())
.map(User::getInstance)
.map(User::getPlayer);
if (owner.isPresent()) {
// Parse for the player's name first (in case placeholders might need it)
customName = customName.replace(TextVariables.NAME, owner.get().getName());
// Now parse the placeholders
customName = plugin.getPlaceholdersManager().replacePlaceholders(owner.get(), customName);
}
/**
* Spawn an entity
* @param k the blueprint entity definition
* @param location location
* @param island island
* @return true if Bukkit entity spawned, false if MythicMob entity spawned
*/
static boolean spawnBlueprintEntity(BlueprintEntity k, Location location, Island island) {
if (k.getMythicMobsRecord() != null && plugin.getHooks().getHook("MythicMobs")
.filter(mmh -> mmh instanceof MythicMobsHook)
.map(mmh -> ((MythicMobsHook) mmh).spawnMythicMob(k.getMythicMobsRecord(), location))
.orElse(false)) {
// MythicMob has spawned.
return false;
}
LivingEntity e = (LivingEntity) location.getWorld().spawnEntity(location, k.getType());
if (k.getCustomName() != null) {
String customName = k.getCustomName();
if (island != null) {
// Parse any placeholders in the entity's name, if the owner's connected (he should)
Optional<Player> owner = Optional.ofNullable(island.getOwner()).map(User::getInstance)
.map(User::getPlayer);
if (owner.isPresent()) {
// Parse for the player's name first (in case placeholders might need it)
customName = customName.replace(TextVariables.NAME, owner.get().getName());
// Now parse the placeholders
customName = plugin.getPlaceholdersManager().replacePlaceholders(owner.get(), customName);
}
// Actually set the custom name
e.setCustomName(customName);
}
k.configureEntity(e);
}));
// Actually set the custom name
e.setCustomName(customName);
}
k.configureEntity(e);
return true;
}
/**

View File

@ -144,7 +144,10 @@ commands:
parameters: <player>
description: transfers island ownership to the player
already-owner: '&c [name] is already the owner of this island!'
must-be-on-island: '&c You must be on the island to set the owner'
confirmation: '&a Are you sure you want to set [name] to be the owner of the island at [xyz]?'
success: '&b [name]&a is now the owner of this island.'
extra-islands: '&c Warning: this player now owns [number] islands. This is more than allowed by settings or perms: [max].'
range:
description: admin island range command
invalid-value:
@ -783,6 +786,7 @@ commands:
errors:
cant-demote-yourself: '&c You can''t demote yourself!'
cant-demote: '&c You can''t demote higher ranks!'
must-be-member: '&c Player must be an island member!'
failure: '&c Player cannot be demoted any further!'
success: '&a Demoted [name] to [rank]'
promote:
@ -791,6 +795,7 @@ commands:
errors:
cant-promote-yourself: '&c You can''t promote yourself!'
cant-promote: '&c You can''t promote above your rank!'
must-be-member: '&c Player must be an island member!'
failure: '&c Player cannot be promoted any further!'
success: '&a Promoted [name] to [rank]'
setowner:

View File

@ -1,7 +1,7 @@
name: BentoBox
main: world.bentobox.bentobox.BentoBox
version: ${project.version}${build.number}
api-version: "1.18"
api-version: "1.20"
authors: [tastybento, Poslovitch]
contributors: ["The BentoBoxWorld Community"]
@ -17,15 +17,13 @@ softdepend:
- Vault
- PlaceholderAPI
- dynmap
- WorldBorderAPI
- BsbMongo
- WorldGeneratorApi
- AdvancedChests
- LangUtils
- WildStacker
- LuckPerms
- HolographicDisplays
- EconomyPlus
- MythicMobs
libraries:
- mysql:mysql-connector-java:${mysql.version}

View File

@ -40,6 +40,7 @@ import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.HooksManager;
import world.bentobox.bentobox.managers.LocalesManager;
/**
@ -73,6 +74,11 @@ public class AdminBlueprintLoadCommandTest {
// Set up plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
// Hooks
HooksManager hooksManager = mock(HooksManager.class);
when(hooksManager.getHook(anyString())).thenReturn(Optional.empty());
when(plugin.getHooks()).thenReturn(hooksManager);
// Blueprints Manager
when(plugin.getBlueprintsManager()).thenReturn(bm);

View File

@ -18,6 +18,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Bukkit;
@ -41,6 +42,7 @@ import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.blueprints.BlueprintClipboard;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.HooksManager;
import world.bentobox.bentobox.managers.LocalesManager;
/**
@ -58,7 +60,7 @@ public class AdminBlueprintSaveCommandTest {
private GameModeAddon addon;
@Mock
private User user;
private BlueprintClipboard clip = new BlueprintClipboard();
private BlueprintClipboard clip;
private UUID uuid = UUID.randomUUID();
private File blueprintsFolder;
@Mock
@ -72,6 +74,12 @@ public class AdminBlueprintSaveCommandTest {
// Set up plugin
BentoBox plugin = mock(BentoBox.class);
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
// Hooks
HooksManager hooksManager = mock(HooksManager.class);
when(hooksManager.getHook(anyString())).thenReturn(Optional.empty());
when(plugin.getHooks()).thenReturn(hooksManager);
clip = new BlueprintClipboard();
// Blueprints Manager
when(plugin.getBlueprintsManager()).thenReturn(bm);
@ -109,7 +117,6 @@ public class AdminBlueprintSaveCommandTest {
PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
absc = new AdminBlueprintSaveCommand(ac);
}

View File

@ -1,38 +1,45 @@
package world.bentobox.bentobox.api.commands.admin.team;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.util.Vector;
import org.eclipse.jdt.annotation.NonNull;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.TestWorldSettings;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
@ -40,6 +47,7 @@ import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.LocalesManager;
import world.bentobox.bentobox.managers.PlaceholdersManager;
import world.bentobox.bentobox.managers.PlayersManager;
import world.bentobox.bentobox.util.Util;
@ -53,16 +61,19 @@ public class AdminTeamSetownerCommandTest {
@Mock
private CompositeCommand ac;
private UUID uuid;
private UUID uuid = UUID.randomUUID();
@Mock
private User user;
@Mock
private IslandsManager im;
@Mock
private PlayersManager pm;
private UUID notUUID;
private UUID notUUID = UUID.randomUUID();
@Mock
private Island island;
private AdminTeamSetownerCommand itl;
@Mock
private @NonNull Location location;
/**
*/
@ -73,35 +84,57 @@ public class AdminTeamSetownerCommandTest {
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
Util.setPlugin(plugin);
Settings settings = new Settings();
// Settings
when(plugin.getSettings()).thenReturn(settings);
// Command manager
CommandsManager cm = mock(CommandsManager.class);
when(plugin.getCommandsManager()).thenReturn(cm);
// Player
Player p = mock(Player.class);
when(p.getUniqueId()).thenReturn(uuid);
when(p.getName()).thenReturn("tastybento");
User.getInstance(p);
// Sometimes use Mockito.withSettings().verboseLogging()
when(user.isOp()).thenReturn(false);
uuid = UUID.randomUUID();
notUUID = UUID.randomUUID();
while (notUUID.equals(uuid)) {
notUUID = UUID.randomUUID();
}
when(user.getUniqueId()).thenReturn(uuid);
when(user.getPlayer()).thenReturn(p);
when(user.getName()).thenReturn("tastybento");
when(user.getTranslation(anyString()))
.thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, String.class));
when(user.getTranslation(anyString(), anyString(), anyString(), anyString(), anyString()))
.thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, String.class));
User.setPlugin(plugin);
// Locales & Placeholders
LocalesManager lm = mock(LocalesManager.class);
when(lm.get(any(), any())).thenAnswer((Answer<String>) invocation -> invocation.getArgument(1, String.class));
PlaceholdersManager phm = mock(PlaceholdersManager.class);
when(plugin.getPlaceholdersManager()).thenReturn(phm);
when(phm.replacePlaceholders(any(), any()))
.thenAnswer((Answer<String>) invocation -> invocation.getArgument(1, String.class));
when(plugin.getLocalesManager()).thenReturn(lm);
// Parent command has no aliases
when(ac.getSubCommandAliases()).thenReturn(new HashMap<>());
// Island World Manager
IslandWorldManager iwm = mock(IslandWorldManager.class);
when(plugin.getIWM()).thenReturn(iwm);
@NonNull
WorldSettings worldSettings = new TestWorldSettings();
when(iwm.getWorldSettings(any())).thenReturn(worldSettings);
// Location
when(location.toVector()).thenReturn(new Vector(1, 2, 3));
// Player has island to begin with
when(im.hasIsland(any(), any(UUID.class))).thenReturn(true);
when(im.hasIsland(any(), any(User.class))).thenReturn(true);
when(island.getOwner()).thenReturn(uuid);
when(island.getCenter()).thenReturn(location);
when(im.getPrimaryIsland(any(), any())).thenReturn(island);
when(plugin.getIslands()).thenReturn(im);
@ -115,15 +148,13 @@ public class AdminTeamSetownerCommandTest {
PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getScheduler()).thenReturn(sch);
// Locales
LocalesManager lm = mock(LocalesManager.class);
when(lm.get(any(), any())).thenReturn("mock translation");
when(plugin.getLocalesManager()).thenReturn(lm);
// Plugin Manager
PluginManager pim = mock(PluginManager.class);
when(Bukkit.getPluginManager()).thenReturn(pim);
// DUT
itl = new AdminTeamSetownerCommand(ac);
}
@After
@ -133,56 +164,45 @@ public class AdminTeamSetownerCommandTest {
}
/**
* Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}.
* Test method for {@link AdminTeamSetownerCommand#canExecute(User, String, List)}.
*/
@Test
public void testExecuteNoTarget() {
AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac);
assertFalse(itl.execute(user, itl.getLabel(), new ArrayList<>()));
assertFalse(itl.canExecute(user, itl.getLabel(), new ArrayList<>()));
// Show help
verify(user).sendMessage("commands.help.header", TextVariables.LABEL, "commands.help.console");
}
/**
* Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}.
* Test method for {@link AdminTeamSetownerCommand#setup()}
*/
@Test
public void testSetup() {
assertEquals("commands.admin.team.setowner.description", itl.getDescription());
assertEquals("commands.admin.team.setowner.parameters", itl.getParameters());
assertTrue(itl.isOnlyPlayer());
assertEquals("mod.team.setowner", itl.getPermission());
}
/**
* Test method for {@link AdminTeamSetownerCommand#canExecute(User, String, List)}.
*/
@Test
public void testExecuteUnknownPlayer() {
AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac);
String[] name = { "tastybento" };
when(pm.getUUID(any())).thenReturn(null);
assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name)));
verify(user).sendMessage("general.errors.unknown-player", "[name]", name[0]);
assertFalse(itl.canExecute(user, itl.getLabel(), List.of("tastybento")));
verify(user).sendMessage("general.errors.unknown-player", "[name]", "tastybento");
}
/**
* Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}.
*/
@Test
public void testExecutePlayerNotInTeam() {
AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac);
String[] name = { "tastybento" };
when(pm.getUUID(any())).thenReturn(notUUID);
// when(im.getMembers(any(), any())).thenReturn(new HashSet<>());
assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name)));
verify(user).sendMessage(eq("general.errors.not-in-team"));
}
/**
* Test method for {@link AdminTeamSetownerCommand#execute(User, String, List)}.
* Test method for {@link AdminTeamSetownerCommand#canExecute(User, String, List)}.
*/
@Test
public void testExecuteMakeOwnerAlreadyOwner() {
when(im.inTeam(any(), any())).thenReturn(true);
Island is = mock(Island.class);
when(im.getIsland(any(), any(UUID.class))).thenReturn(is);
String[] name = {"tastybento"};
when(pm.getUUID(any())).thenReturn(notUUID);
when(pm.getName(any())).thenReturn(name[0]);
when(island.getOwner()).thenReturn(notUUID);
AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac);
assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name)));
verify(user).sendMessage("commands.admin.team.setowner.already-owner", TextVariables.NAME, name[0]);
when(im.getIslandAt(any())).thenReturn(Optional.of(island));
when(island.getOwner()).thenReturn(uuid);
when(Util.getUUID("tastybento")).thenReturn(uuid);
assertFalse(itl.canExecute(user, itl.getLabel(), List.of("tastybento")));
verify(user).sendMessage("commands.admin.team.setowner.already-owner", TextVariables.NAME, "tastybento");
}
/**
@ -190,28 +210,44 @@ public class AdminTeamSetownerCommandTest {
*/
@Test
public void testExecuteSuccess() {
// Player is a team member, not an owner
when(im.hasIsland(any(), any(UUID.class))).thenReturn(false);
when(im.hasIsland(any(), any(User.class))).thenReturn(false);
when(im.inTeam(any(), any())).thenReturn(true);
Island is = mock(Island.class);
when(im.getIsland(any(), any(UUID.class))).thenReturn(is);
String[] name = {"tastybento"};
when(pm.getUUID(any())).thenReturn(notUUID);
when(pm.getName(any())).thenReturn(name[0]);
// Owner
//when(im.getOwner(any(), eq(notUUID))).thenReturn(uuid);
when(pm.getName(eq(uuid))).thenReturn("owner");
// Members
Set<UUID> members = new HashSet<>();
members.add(uuid);
members.add(notUUID);
//when(im.getMembers(any(), any())).thenReturn(members);
when(im.getIslandAt(any())).thenReturn(Optional.of(island));
when(island.getOwner()).thenReturn(notUUID);
when(Util.getUUID("tastybento")).thenReturn(uuid);
AdminTeamSetownerCommand itl = new AdminTeamSetownerCommand(ac);
assertTrue(itl.execute(user, itl.getLabel(), Arrays.asList(name)));
assertTrue(itl.canExecute(user, itl.getLabel(), List.of("tastybento")));
assertTrue(itl.execute(user, itl.getLabel(), List.of("tastybento")));
// Add other verifications
verify(im).setOwner(any(), eq(user), eq(notUUID));
verify(user).sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, name[0]);
verify(user).getTranslation("commands.admin.team.setowner.confirmation", TextVariables.NAME, "tastybento",
TextVariables.XYZ, "1,2,3");
}
/**
* Test method for {@link AdminTeamSetownerCommand#changeOwner(User)}
*/
@Test
public void testChangeOwner() {
when(im.getIslandAt(any())).thenReturn(Optional.of(island));
when(island.getOwner()).thenReturn(notUUID);
when(Util.getUUID("tastybento")).thenReturn(uuid);
assertTrue(itl.canExecute(user, itl.getLabel(), List.of("tastybento")));
itl.changeOwner(user);
// Add other verifications
verify(user).sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, "tastybento");
}
/**
* Test method for {@link AdminTeamSetownerCommand#changeOwner(User)}
*/
@Test
public void testChangeOwnerNoOwner() {
when(im.getIslandAt(any())).thenReturn(Optional.of(island));
when(island.getOwner()).thenReturn(null);
when(Util.getUUID("tastybento")).thenReturn(uuid);
assertTrue(itl.canExecute(user, itl.getLabel(), List.of("tastybento")));
itl.changeOwner(user);
// Add other verifications
verify(user).sendMessage("commands.admin.team.setowner.success", TextVariables.NAME, "tastybento");
}
}

View File

@ -37,6 +37,7 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.CommandsManager;
@ -195,6 +196,20 @@ public class IslandSethomeCommandTest {
verify(user).sendMessage("commands.island.sethome.must-be-on-your-island");
}
/**
* Test method for
* {@link world.bentobox.bentobox.api.commands.island.IslandSethomeCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.
*/
@Test
public void testCanExecuteTooManyHomes() {
when(im.getMaxHomes(island)).thenReturn(10);
when(im.getNumberOfHomesIfAdded(eq(island), anyString())).thenReturn(11);
IslandSethomeCommand isc = new IslandSethomeCommand(ic);
assertFalse(isc.canExecute(user, "island", Collections.emptyList()));
verify(user).sendMessage("commands.island.sethome.too-many-homes", TextVariables.NUMBER, "10");
verify(user).sendMessage("commands.island.sethome.homes-are");
}
/**
* Test method for
* {@link world.bentobox.bentobox.api.commands.island.IslandSethomeCommand#canExecute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}.

View File

@ -132,6 +132,7 @@ public class IslandTeamInviteCommandTest extends RanksManagerBeforeClassTest {
// Parent command has no aliases
when(ic.getSubCommandAliases()).thenReturn(new HashMap<>());
when(ic.getWorld()).thenReturn(world);
when(ic.getPlugin()).thenReturn(plugin);
// Island
islandUUID = UUID.randomUUID();

View File

@ -102,6 +102,7 @@ public class IslandTeamPromoteCommandTest extends RanksManagerBeforeClassTest {
when(pm.getUser("target")).thenReturn(target);
when(target.getName()).thenReturn("target");
when(target.getDisplayName()).thenReturn("Target");
when(target.getUniqueId()).thenReturn(uuid);
// Managers
when(plugin.getIslands()).thenReturn(im);

View File

@ -4,11 +4,14 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import java.util.Optional;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@ -29,6 +32,7 @@ import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.managers.HooksManager;
/**
* @author tastybento
@ -56,6 +60,10 @@ public class BlueprintClipboardTest {
public void setUp() throws Exception {
// Set up plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
// Hooks
HooksManager hooksManager = mock(HooksManager.class);
when(hooksManager.getHook(anyString())).thenReturn(Optional.empty());
when(plugin.getHooks()).thenReturn(hooksManager);
// User
when(user.getTranslation(Mockito.anyString())).thenAnswer((Answer<String>) invocation -> invocation.getArgument(0, String.class));

View File

@ -1,5 +1,6 @@
package world.bentobox.bentobox.blueprints.dataobjects;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import java.util.HashMap;
@ -26,6 +27,8 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity.MythicMobRecord;
/**
* @author tastybento
*
@ -84,10 +87,10 @@ public class BlueprintEntityTest {
blueprint.configureEntity(villager);
Assert.assertEquals(Profession.LIBRARIAN, villager.getProfession());
Assert.assertEquals(100, villager.getVillagerExperience());
Assert.assertEquals(2, villager.getVillagerLevel());
Assert.assertEquals(Villager.Type.PLAINS, villager.getVillagerType());
assertEquals(Profession.LIBRARIAN, villager.getProfession());
assertEquals(100, villager.getVillagerExperience());
assertEquals(2, villager.getVillagerLevel());
assertEquals(Villager.Type.PLAINS, villager.getVillagerType());
}
@Test
@ -99,7 +102,7 @@ public class BlueprintEntityTest {
blueprint.configureEntity(sheep);
Assert.assertEquals(DyeColor.BLUE, sheep.getColor());
assertEquals(DyeColor.BLUE, sheep.getColor());
}
@Test
@ -147,7 +150,7 @@ public class BlueprintEntityTest {
blueprint.configureEntity(horse);
Assert.assertEquals(50, horse.getDomestication());
assertEquals(50, horse.getDomestication());
}
@Test
@ -159,7 +162,7 @@ public class BlueprintEntityTest {
blueprint.configureEntity(horse);
Assert.assertEquals(Style.WHITE_DOTS, horse.getStyle());
assertEquals(Style.WHITE_DOTS, horse.getStyle());
}
@Test
@ -167,13 +170,13 @@ public class BlueprintEntityTest {
BlueprintEntity blueprint = new BlueprintEntity();
blueprint.setColor(DyeColor.RED);
Assert.assertEquals(DyeColor.RED, blueprint.getColor());
assertEquals(DyeColor.RED, blueprint.getColor());
blueprint.setType(EntityType.CREEPER);
Assert.assertEquals(EntityType.CREEPER, blueprint.getType());
assertEquals(EntityType.CREEPER, blueprint.getType());
blueprint.setCustomName("My Entity");
Assert.assertEquals("My Entity", blueprint.getCustomName());
assertEquals("My Entity", blueprint.getCustomName());
blueprint.setTamed(true);
Assert.assertTrue(blueprint.getTamed());
@ -185,27 +188,35 @@ public class BlueprintEntityTest {
Assert.assertFalse(blueprint.getAdult());
blueprint.setDomestication(75);
Assert.assertEquals(75, blueprint.getDomestication().intValue());
assertEquals(75, blueprint.getDomestication().intValue());
Map<Integer, ItemStack> inventory = new HashMap<>();
inventory.put(1, new ItemStack(Material.DIAMOND));
blueprint.setInventory(inventory);
Assert.assertEquals(inventory, blueprint.getInventory());
assertEquals(inventory, blueprint.getInventory());
blueprint.setStyle(Style.WHITE);
Assert.assertEquals(Style.WHITE, blueprint.getStyle());
assertEquals(Style.WHITE, blueprint.getStyle());
blueprint.setLevel(5);
Assert.assertEquals(5, blueprint.getLevel().intValue());
assertEquals(5, blueprint.getLevel().intValue());
blueprint.setProfession(Profession.FARMER);
Assert.assertEquals(Profession.FARMER, blueprint.getProfession());
assertEquals(Profession.FARMER, blueprint.getProfession());
blueprint.setExperience(500);
Assert.assertEquals(500, blueprint.getExperience().intValue());
assertEquals(500, blueprint.getExperience().intValue());
blueprint.setVillagerType(Villager.Type.TAIGA);
Assert.assertEquals(Villager.Type.TAIGA, blueprint.getVillagerType());
assertEquals(Villager.Type.TAIGA, blueprint.getVillagerType());
}
@Test
public void testMythicMobs() {
BlueprintEntity blueprint = new BlueprintEntity();
MythicMobRecord mmr = new MythicMobRecord("string", "string2", 10D, 1F, "string3");
blueprint.setMythicMobsRecord(mmr);
assertEquals(mmr, blueprint.getMythicMobsRecord());
}
}

View File

@ -0,0 +1,165 @@
package world.bentobox.bentobox.hooks;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import io.lumine.mythic.api.mobs.MythicMob;
import io.lumine.mythic.bukkit.MythicBukkit;
import io.lumine.mythic.core.mobs.ActiveMob;
import io.lumine.mythic.core.mobs.MobExecutor;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity.MythicMobRecord;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ BentoBox.class, Bukkit.class, MythicBukkit.class })
public class MythicMobsHookTest {
@Mock
private BentoBox plugin;
@Mock
private PluginManager pim;
@Mock
private Plugin mythicMobs;
@Mock
private Location location;
@Mock
private World world;
// DUT
MythicMobsHook hook;
@Mock
private MythicBukkit mythicBukkit;
@Mock
private MobExecutor mm;
@Mock
private MythicMob mythicMob;
@Mock
private ActiveMob activeMob;
@Mock
private Entity entity;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
// Set up plugin
plugin = mock(BentoBox.class);
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
// Bukkit
PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
when(Bukkit.getPluginManager()).thenReturn(pim);
when(pim.getPlugin("MythicMobs")).thenReturn(mythicMobs);
// Location
when(world.getName()).thenReturn("bskyblock");
when(location.getWorld()).thenReturn(world);
// Entity
when(entity.getUniqueId()).thenReturn(UUID.randomUUID());
// MythicMobs
PowerMockito.mockStatic(MythicBukkit.class, Mockito.RETURNS_MOCKS);
when(MythicBukkit.inst()).thenReturn(mythicBukkit);
when(mythicBukkit.getMobManager()).thenReturn(mm);
when(mm.getMythicMob(anyString())).thenReturn(Optional.of(mythicMob));
when(activeMob.getDisplayName()).thenReturn("Minion");
when(activeMob.getMobType()).thenReturn("GIANT");
when(activeMob.getStance()).thenReturn("default");
when(activeMob.getLevel()).thenReturn(2.5D);
when(activeMob.getPower()).thenReturn(33.2F);
when(mm.getActiveMob(any())).thenReturn(Optional.of(activeMob));
hook = new MythicMobsHook();
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
}
/**
* Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#hook()}.
*/
@Test
public void testHook() {
assertTrue(hook.hook());
}
/**
* Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#getFailureCause()}.
*/
@Test
public void testGetFailureCause() {
assertNull(hook.getFailureCause());
}
/**
* Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#MythicMobsHook()}.
*/
@Test
public void testMythicMobsHook() {
assertNotNull(hook);
assertEquals(Material.CREEPER_HEAD, hook.getIcon());
}
/**
* Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#isMythicMob(org.bukkit.entity.Entity)}.
*/
@Test
public void testIsMythicMob() {
assertFalse(hook.isMythicMob(entity));
}
/**
* Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#getMythicMob(org.bukkit.entity.Entity)}.
*/
@Test
public void testGetMythicMob() {
MythicMobRecord mmr = hook.getMythicMob(entity);
assertEquals("GIANT", mmr.type());
assertEquals("Minion", mmr.displayName());
assertEquals("default", mmr.stance());
assertEquals(2.5D, mmr.level(), 0D);
assertEquals(33.2F, mmr.power(), 0F);
}
/**
* Test method for {@link world.bentobox.bentobox.hooks.MythicMobsHook#spawnMythicMob(world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity.MythicMobRecord, org.bukkit.Location)}.
*/
@Test
public void testSpawnMythicMob() {
MythicMobRecord mmr = hook.getMythicMob(entity);
assertTrue(hook.spawnMythicMob(mmr, location));
verify(mm).getMythicMob("GIANT");
}
}

View File

@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -16,6 +17,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Comparator;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@ -33,6 +35,7 @@ import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
@ -128,13 +131,20 @@ public class BlueprintClipboardManagerTest {
blueprintFolder = new File("blueprints");
// Clear any residual files
tearDown();
// Set up plugin
BentoBox plugin = mock(BentoBox.class);
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
// Hooks
HooksManager hooksManager = mock(HooksManager.class);
when(hooksManager.getHook(anyString())).thenReturn(Optional.empty());
when(plugin.getHooks()).thenReturn(hooksManager);
PowerMockito.mockStatic(Bukkit.class);
BlockData blockData = mock(BlockData.class);
when(Bukkit.createBlockData(any(Material.class))).thenReturn(blockData);
when(blockData.getAsString()).thenReturn("test123");
when(server.getBukkitVersion()).thenReturn("version");
when(Bukkit.getServer()).thenReturn(server);
}
/**

View File

@ -268,7 +268,7 @@ public class NewIslandTest {
verify(builder, times(2)).build();
verify(bpb).getUniqueId();
verify(ice).getBlueprintBundle();
verify(pm).setDeaths(eq(world), eq(uuid), eq(0));
verify(pm).setDeaths(world, uuid, 0);
verify(im, never()).setHomeLocation(eq(user), any());
}
@ -281,13 +281,13 @@ public class NewIslandTest {
NewIsland.builder().addon(addon).name(NAME).player(user).reason(Reason.CREATE).build();
PowerMockito.mockStatic(Bukkit.class);
// Verifications
verify(im).save(eq(island));
verify(im).save(island);
verify(island).setFlagsDefaults();
verify(bpm).paste(eq(addon), eq(island), eq(NAME), any(Runnable.class), eq(true));
verify(builder, times(2)).build();
verify(bpb).getUniqueId();
verify(ice).getBlueprintBundle();
verify(pm).setDeaths(eq(world), eq(uuid), eq(0));
verify(pm).setDeaths(world, uuid, 0);
verify(im, never()).setHomeLocation(eq(user), any());
}
@ -305,7 +305,7 @@ public class NewIslandTest {
verify(builder, times(2)).build();
verify(bpb).getUniqueId();
verify(ice).getBlueprintBundle();
verify(pm).setDeaths(eq(world), eq(uuid), eq(0));
verify(pm).setDeaths(world, uuid, 0);
verify(im, never()).setHomeLocation(eq(user), any());
verify(island).setProtectionRange(eq(20));
verify(island).setReserved(eq(false));
@ -326,7 +326,7 @@ public class NewIslandTest {
verify(builder, times(2)).build();
verify(bpb).getUniqueId();
verify(ice).getBlueprintBundle();
verify(pm).setDeaths(eq(world), eq(uuid), eq(0));
verify(pm).setDeaths(world, uuid, 0);
verify(im, never()).setHomeLocation(eq(user), any());
verify(island).setProtectionRange(eq(20));
//verify(plugin).logError("New island for user tastybento was not reserved!");
@ -348,7 +348,7 @@ public class NewIslandTest {
verify(builder, times(2)).build();
verify(bpb).getUniqueId();
verify(ice).getBlueprintBundle();
verify(pm).setDeaths(eq(world), eq(uuid), eq(0));
verify(pm).setDeaths(world, uuid, 0);
verify(im, never()).setHomeLocation(eq(user), any());
verify(island).setProtectionRange(eq(20));
verify(plugin).logError("New island for user tastybento was not reserved!");

View File

@ -1,9 +1,12 @@
package world.bentobox.bentobox.util;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -25,10 +28,13 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.block.sign.Side;
import org.bukkit.block.sign.SignSide;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@ -43,7 +49,11 @@ import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity.MythicMobRecord;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.hooks.MythicMobsHook;
import world.bentobox.bentobox.managers.HooksManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.LocalesManager;
import world.bentobox.bentobox.managers.PlayersManager;
@ -79,10 +89,20 @@ public class DefaultPasteUtilTest {
@Mock(extraInterfaces = {org.bukkit.block.Sign.class})
BlockState sign;
@Mock
private PlayersManager pm;
@Mock
private MythicMobsHook mythicMobsHook;
@Mock
private BlueprintEntity blueprintEntity;
@Mock
private Location location;
@Mock
private LivingEntity livingEntity;
@Mock
private World world;
@Mock
private PlayersManager pm;
private HooksManager hooksManager;
/**
@ -110,6 +130,11 @@ public class DefaultPasteUtilTest {
when(plugin.getLocalesManager()).thenReturn(localesManager);
when(localesManager.getOrDefault(any(), anyString(), anyString())).thenReturn("translated");
when(location.getWorld()).thenReturn(world);
// Hooks
when(hooksManager.getHook("MythicMobs")).thenReturn(Optional.of(mythicMobsHook));
when(plugin.getHooks()).thenReturn(hooksManager);
when(plugin.getPlayers()).thenReturn(pm);
}
@ -188,4 +213,32 @@ public class DefaultPasteUtilTest {
List<String> capturedLines = lineCaptor.getAllValues();
Assert.assertEquals(linesTranslated, capturedLines);
}
@Ignore
@Test
public void testSpawnBlueprintEntity_WithMythicMobs() {
// Set up conditions to satisfy the mythic mobs spawning logic
MythicMobRecord mmr = new MythicMobRecord("string", "string2", 10D, 1F, "string3");
when(blueprintEntity.getMythicMobsRecord()).thenReturn(mmr);
when(mythicMobsHook.spawnMythicMob(mmr, location)).thenReturn(true);
// This test works fine if there is a System.out.println() in the code. I assume some optimization is being done in compilation
assertFalse(DefaultPasteUtil.spawnBlueprintEntity(blueprintEntity, location, island));
// Verify the mythic mob was spawned, and the method returned early
verify(mythicMobsHook).spawnMythicMob(mmr, location);
verify(world, never()).spawnEntity(any(Location.class), any(EntityType.class));
}
@Test
public void testSpawnBlueprintEntity_WithoutMythicMobs() {
// Set up conditions where MythicMobs should not be spawned
when(hooksManager.getHook("MythicMobs")).thenReturn(Optional.empty());
assertTrue(DefaultPasteUtil.spawnBlueprintEntity(blueprintEntity, location, island));
// Verify a regular entity was spawned instead
verify(world).spawnEntity(location, blueprintEntity.getType());
}
}