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