diff --git a/pom.xml b/pom.xml
index ffd30e9..c019610 100644
--- a/pom.xml
+++ b/pom.xml
@@ -123,8 +123,12 @@
- spigot-repo
- https://hub.spigotmc.org/nexus/content/repositories/snapshots
+ jitpack.io
+ https://jitpack.io
+
+
+ papermc
+ https://repo.papermc.io/repository/maven-public/
minecraft-repo
@@ -153,18 +157,17 @@
-
- org.spigotmc
- spigot-api
- ${spigot.version}
- provided
-
+ com.github.MockBukkit
+ MockBukkit
+ v1.21-SNAPSHOT
+ test
+
- org.spigotmc
- plugin-annotations
- 1.2.3-SNAPSHOT
- compile
+ io.papermc.paper
+ paper-api
+ 1.21.10-R0.1-SNAPSHOT
+ provided
@@ -223,7 +226,6 @@
${items-adder.version}
provided
-
@@ -292,7 +294,6 @@
3.0.0-M5
- ${argLine}
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
diff --git a/src/main/java/world/bentobox/aoneblock/AOneBlock.java b/src/main/java/world/bentobox/aoneblock/AOneBlock.java
index ecde518..3ea3ba7 100644
--- a/src/main/java/world/bentobox/aoneblock/AOneBlock.java
+++ b/src/main/java/world/bentobox/aoneblock/AOneBlock.java
@@ -89,7 +89,15 @@ public class AOneBlock extends GameModeAddon {
.listener(bossBar)
.defaultSetting(true)
.build();
-
+ /**
+ * Flag to enable or disable the OneBlock action bar.
+ */
+ public final Flag ONEBLOCK_ACTIONBAR = new Flag.Builder("ONEBLOCK_ACTIONBAR", Material.IRON_BARS)
+ .mode(Mode.BASIC)
+ .type(Type.SETTING)
+ .listener(bossBar)
+ .defaultSetting(true)
+ .build();
/**
* Flag to set who can break the magic block.
*/
diff --git a/src/main/java/world/bentobox/aoneblock/commands/island/IslandActionBarCommand.java b/src/main/java/world/bentobox/aoneblock/commands/island/IslandActionBarCommand.java
new file mode 100644
index 0000000..4ac71d6
--- /dev/null
+++ b/src/main/java/world/bentobox/aoneblock/commands/island/IslandActionBarCommand.java
@@ -0,0 +1,37 @@
+package world.bentobox.aoneblock.commands.island;
+
+import java.util.List;
+
+import world.bentobox.aoneblock.AOneBlock;
+import world.bentobox.bentobox.api.commands.CompositeCommand;
+import world.bentobox.bentobox.api.user.User;
+
+public class IslandActionBarCommand extends CompositeCommand {
+
+ private AOneBlock addon;
+
+ public IslandActionBarCommand(CompositeCommand islandCommand, String label, String[] aliases)
+ {
+ super(islandCommand, label, aliases);
+ }
+
+ @Override
+ public void setup() {
+ setDescription("aoneblock.commands.island.actionbar.description");
+ setOnlyPlayer(true);
+ // Permission
+ setPermission("island.actionbar");
+ addon = getAddon();
+ }
+
+ @Override
+ public boolean execute(User user, String label, List args) {
+ addon.getBossBar().toggleUser(user);
+ getIslands().getIslandAt(user.getLocation()).ifPresent(i -> {
+ if (!i.isAllowed(addon.ONEBLOCK_ACTIONBAR)) {
+ user.sendMessage("aoneblock.actionbar.not-active");
+ }
+ });
+ return true;
+ }
+}
diff --git a/src/main/java/world/bentobox/aoneblock/listeners/BossBarListener.java b/src/main/java/world/bentobox/aoneblock/listeners/BossBarListener.java
index 5efbf3a..822f377 100644
--- a/src/main/java/world/bentobox/aoneblock/listeners/BossBarListener.java
+++ b/src/main/java/world/bentobox/aoneblock/listeners/BossBarListener.java
@@ -17,6 +17,9 @@
import org.bukkit.event.player.PlayerQuitEvent;
import org.eclipse.jdt.annotation.NonNull;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import world.bentobox.aoneblock.AOneBlock;
import world.bentobox.aoneblock.dataobjects.OneBlockIslands;
import world.bentobox.aoneblock.events.MagicBlockEvent;
@@ -30,6 +33,12 @@
public class BossBarListener implements Listener {
private static final String AONEBLOCK_BOSSBAR = "aoneblock.bossbar";
+ private static final String AONEBLOCK_ACTIONBAR = "aoneblock.actionbar";
+
+ private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.builder()
+ .character('&')
+ .hexColors() // Enables support for modern hex codes (e.g., FF0000) alongside legacy codes.
+ .build();
public BossBarListener(AOneBlock addon) {
super();
@@ -45,12 +54,14 @@ public BossBarListener(AOneBlock addon) {
public void onBreakBlockEvent(MagicBlockEvent e) {
// Update boss bar
tryToShowBossBar(e.getPlayerUUID(), e.getIsland());
+ tryToShowActionBar(e.getPlayerUUID(), e.getIsland());
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onEnterIsland(IslandEnterEvent event) {
if (addon.inWorld(event.getIsland().getWorld())) {
tryToShowBossBar(event.getPlayerUUID(), event.getIsland());
+ tryToShowActionBar(event.getPlayerUUID(), event.getIsland());
}
}
@@ -59,10 +70,56 @@ public void onFlagChange(FlagSettingChangeEvent e) {
if (e.getEditedFlag() == addon.ONEBLOCK_BOSSBAR) {
// Show to players on island. If it isn't allowed then this will clean up the boss bar too
e.getIsland().getPlayersOnIsland().stream().map(Player::getUniqueId)
- .forEach(uuid -> this.tryToShowBossBar(uuid, e.getIsland()));
+ .forEach(uuid -> {
+ tryToShowBossBar(uuid, e.getIsland());
+ tryToShowActionBar(uuid, e.getIsland());
+ });
}
}
+ /**
+ * Converts a string containing Bukkit color codes ('&') into an Adventure Component.
+ *
+ * @param legacyString The string with Bukkit color and format codes.
+ * @return The resulting Adventure Component.
+ */
+ public static Component bukkitToAdventure(String legacyString) {
+ if (legacyString == null) {
+ return Component.empty();
+ }
+ return LEGACY_SERIALIZER.deserialize(legacyString);
+ }
+
+ private void tryToShowActionBar(UUID uuid, Island island) {
+ User user = User.getInstance(uuid);
+ Player player = Bukkit.getPlayer(uuid);
+
+ // Only show if enabled for island
+ if (!island.isAllowed(addon.ONEBLOCK_ACTIONBAR)) {
+ return;
+ }
+ // Default to showing boss bar unless it is explicitly turned off
+ if (!user.getMetaData(AONEBLOCK_ACTIONBAR).map(MetaDataValue::asBoolean).orElse(true)) {
+ // Remove any boss bar from user if they are in the world
+ removeBar(user, island);
+ // Do not show a boss bar
+ return;
+ }
+ // Get the progress
+ @NonNull
+ OneBlockIslands obi = addon.getOneBlocksIsland(island);
+
+ // --- Create the Action Bar Component ---
+ int numBlocksToGo = addon.getOneBlockManager().getNextPhaseBlocks(obi);
+ int phaseBlocks = addon.getOneBlockManager().getPhaseBlocks(obi);
+ int done = phaseBlocks - numBlocksToGo;
+ String translation = user.getTranslationOrNothing("aoneblock.actionbar.status", "[togo]",
+ String.valueOf(numBlocksToGo), "[total]", String.valueOf(phaseBlocks), "[done]", String.valueOf(done),
+ "[phase-name]", obi.getPhaseName(), "[percent-done]",
+ Math.round(addon.getOneBlockManager().getPercentageDone(obi)) + "%");
+ // Send
+ player.sendActionBar(bukkitToAdventure(translation));
+ }
/**
* Try to show the bossbar to the player
* @param uuid player's UUID
@@ -128,7 +185,6 @@ private void tryToShowBossBar(UUID uuid, Island island) {
}
// Save the boss bar for later reference (e.g., when updating or removing)
islandBossBars.put(island, bar);
-
}
private void removeBar(User user, Island island) {
@@ -159,7 +215,7 @@ public void onJoin(PlayerJoinEvent e) {
return;
}
addon.getIslands().getIslandAt(e.getPlayer().getLocation())
- .ifPresent(is -> this.tryToShowBossBar(e.getPlayer().getUniqueId(), is));
+ .ifPresent(is -> this.tryToShowBossBar(e.getPlayer().getUniqueId(), is));
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
@@ -179,7 +235,7 @@ public void toggleUser(User user) {
if (newState) {
// If the player is on an island then show the bar
addon.getIslands().getIslandAt(user.getLocation()).filter(is -> addon.inWorld(is.getWorld()))
- .ifPresent(is -> this.tryToShowBossBar(user.getUniqueId(), is));
+ .ifPresent(is -> this.tryToShowBossBar(user.getUniqueId(), is));
user.sendMessage("aoneblock.commands.island.bossbar.status_on");
} else {
// Remove player from any boss bars. Adding happens automatically
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java
index a2c88e1..0a6d23b 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java
@@ -19,7 +19,7 @@
import java.util.jar.JarFile;
import java.util.stream.Collectors;
-import org.apache.commons.lang.math.NumberUtils;
+import org.apache.commons.lang3.math.NumberUtils;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
@@ -260,7 +260,7 @@ private Map parseFirstBlocksConfig(ConfigurationSection
Map result = new HashMap<>();
for (String key : firstBlocksConfig.getKeys(false)) {
- if (!NumberUtils.isNumber(key)) {
+ if (!NumberUtils.isCreatable(key)) {
addon.logError("Fixed block key must be an integer. " + key);
continue;
}
@@ -335,7 +335,7 @@ private void addHologramLines(OneBlockPhase obPhase, ConfigurationSection fb) {
return;
Map result = new HashMap<>();
for (String key : fb.getKeys(false)) {
- if (!NumberUtils.isNumber(key)) {
+ if (!NumberUtils.isCreatable(key)) {
addon.logError("Fixed block key must be an integer. " + key);
continue;
}
diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml
index 40ed3a3..19a340e 100755
--- a/src/main/resources/locales/en-US.yml
+++ b/src/main/resources/locales/en-US.yml
@@ -24,6 +24,14 @@ protection:
description: |
&b Shows a status bar
&b for each phase.
+
+ ONEBLOCK_ACTIONBAR:
+ name: Action Bar
+ description: |
+ &b Shows a status
+ &b for each phase
+ &b in the Action Bar.
+
aoneblock:
bossbar:
title: "Blocks remaining"
@@ -35,6 +43,9 @@ aoneblock:
# SOLID, SEGMENTED_6, SEGMENTED_10, SEGMENTED_12, SEGMENTED_20
style: SOLID
not-active: "&c Boss Bar is not active for this island"
+ actionbar:
+ status: "&a Phase: &b [phase-name] &d | &a Blocks: &b [done] &d / &b [total] &d | &a Progression: &b [percent-done]"
+ not-active: "&c Action Bar is not active for this island"
commands:
admin:
setcount:
@@ -71,6 +82,10 @@ aoneblock:
description: "toggles phase boss bar"
status_on: "&b Bossbar turned &a on"
status_off: "&b Bossbar turned &c off"
+ actionbar:
+ description: "toggles phase action bar"
+ status_on: "&b Action Bar turned &a on"
+ status_off: "&b Action Bar turned &c off"
setcount:
parameters: ""
description: "set block count to previously completed value"
diff --git a/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java b/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java
index 8014068..e2a5a9f 100644
--- a/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java
+++ b/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java
@@ -7,7 +7,9 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.RETURNS_MOCKS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockitoSession;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -40,6 +42,7 @@
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockbukkit.mockbukkit.MockBukkit;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
@@ -110,7 +113,7 @@ public static void beforeClass() throws IllegalAccessException, InvocationTarget
@After
public void tearDown() throws IOException {
- ServerMocks.unsetBukkitServer();
+ MockBukkit.unmock();
User.clearUsers();
Mockito.framework().clearInlineMocks();
deleteAll(new File("database"));
@@ -132,7 +135,8 @@ private void deleteAll(File file) throws IOException {
*/
@Before
public void setUp() throws Exception {
- Server server = ServerMocks.newServer();
+ PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
+ Server server = MockBukkit.mock();
// Set up plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger());
@@ -174,7 +178,7 @@ public void setUp() throws Exception {
.thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class));
// Server
- PowerMockito.mockStatic(Bukkit.class);
+ PowerMockito.mockStatic(Bukkit.class, RETURNS_MOCKS);
when(Bukkit.getServer()).thenReturn(server);
when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class));
diff --git a/src/test/java/world/bentobox/aoneblock/listeners/BlockProtectTest.java b/src/test/java/world/bentobox/aoneblock/listeners/BlockProtectTest.java
index 561075c..eebed38 100644
--- a/src/test/java/world/bentobox/aoneblock/listeners/BlockProtectTest.java
+++ b/src/test/java/world/bentobox/aoneblock/listeners/BlockProtectTest.java
@@ -20,6 +20,7 @@
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
+import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
@@ -39,11 +40,13 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockbukkit.mockbukkit.MockBukkit;
import org.mockito.Mock;
import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.aoneblock.AOneBlock;
import world.bentobox.aoneblock.Settings;
+import world.bentobox.aoneblock.mocks.ServerMocks;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.IslandsManager;
@@ -75,6 +78,8 @@ public class BlockProtectTest {
*/
@Before
public void setUp() throws Exception {
+
+ Server server = MockBukkit.mock();
when(p.getWorld()).thenReturn(world);
// In World
@@ -105,6 +110,7 @@ public void setUp() throws Exception {
*/
@After
public void tearDown() throws Exception {
+ MockBukkit.unmock();
}
/**
diff --git a/src/test/java/world/bentobox/aoneblock/listeners/NoBlockHandlerTest.java b/src/test/java/world/bentobox/aoneblock/listeners/NoBlockHandlerTest.java
index 5ad0baa..379f85b 100644
--- a/src/test/java/world/bentobox/aoneblock/listeners/NoBlockHandlerTest.java
+++ b/src/test/java/world/bentobox/aoneblock/listeners/NoBlockHandlerTest.java
@@ -68,6 +68,7 @@ public void setUp() throws Exception {
// Location
when(location.getWorld()).thenReturn(world);
+ when(location.clone()).thenReturn(location);
// Block
when(location.getBlock()).thenReturn(block);
@@ -106,7 +107,7 @@ public void testNoBlockHandler() {
*/
@Test
public void testOnRespawnSolidBlock() {
- PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, RespawnReason.DEATH);
+ PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, false, RespawnReason.DEATH);
nbh.onRespawn(event);
verify(block, never()).setType(any(Material.class));
@@ -118,7 +119,7 @@ public void testOnRespawnSolidBlock() {
@Test
public void testOnRespawnAirBlock() {
when(block.isEmpty()).thenReturn(true);
- PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, RespawnReason.DEATH);
+ PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, false, RespawnReason.DEATH);
nbh.onRespawn(event);
verify(block).setType(any(Material.class));
@@ -131,7 +132,7 @@ public void testOnRespawnAirBlock() {
public void testOnRespawnAirBlockWrongWorld() {
when(aob.inWorld(world)).thenReturn(false);
when(block.isEmpty()).thenReturn(true);
- PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, RespawnReason.DEATH);
+ PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, true, RespawnReason.DEATH);
nbh.onRespawn(event);
verify(block, never()).setType(any(Material.class));
@@ -144,7 +145,7 @@ public void testOnRespawnAirBlockWrongWorld() {
public void testOnRespawnAirBlockNoIsland() {
when(im.getIsland(world, ID)).thenReturn(null);
when(block.isEmpty()).thenReturn(true);
- PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, RespawnReason.DEATH);
+ PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, false, RespawnReason.DEATH);
nbh.onRespawn(event);
verify(block, never()).setType(any(Material.class));
diff --git a/src/test/java/world/bentobox/aoneblock/mocks/ServerMocks.java b/src/test/java/world/bentobox/aoneblock/mocks/ServerMocks.java
index cf6c13e..a064773 100644
--- a/src/test/java/world/bentobox/aoneblock/mocks/ServerMocks.java
+++ b/src/test/java/world/bentobox/aoneblock/mocks/ServerMocks.java
@@ -1,5 +1,6 @@
package world.bentobox.aoneblock.mocks;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
@@ -24,6 +25,7 @@
public final class ServerMocks {
+ @SuppressWarnings({ "deprecation", "unchecked" })
public static @NonNull Server newServer() {
Server mock = mock(Server.class);
@@ -66,7 +68,8 @@ public final class ServerMocks {
doReturn(key).when(keyed).getKey();
return keyed;
});
- }).when(registry).get(notNull());
+ // Cast the registry mock to explicitly define the generic type for the 'get' method resolution.
+ }).when((Registry) registry).get(any(NamespacedKey.class));
return registry;
})).when(mock).getRegistry(notNull());