diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a386efb..4e34bb2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,7 @@ on: jobs: publish: - uses: bentoboxworld/.github/.github/workflows/publish-platforms.yml@ca2dcd167e8db4e0f671a976080744dda43801a6 # master + uses: bentoboxworld/.github/.github/workflows/publish-platforms.yml@1f91a0edf72e8c86d671b3b8fdd3121ac6fb88e1 # master with: use_release_asset: "true" # publish the jar attached to the release; do not rebuild hangar_slug: "ChunkBlock" # blank = skip Hangar diff --git a/README.md b/README.md index 2c2c940..ec31a11 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ earn the levels back. - The classic OneBlock loop: 18 phases, thousands of blocks, mobs, and treasure chests. - Territory that grows the way *you* choose — punch the border to claim the next chunk. - A particle curtain shows the frontier; claiming chunks is celebrated in style. -- `/cb chunks` shows a live map of your territory and what you can claim next. +- `/ch chunks` shows a live map of your territory and what you can claim next. ## Installation @@ -34,15 +34,15 @@ commands, and permissions. ## Commands -The player command is `/cb` (alias `/chunkblock`), the admin command `/cbadmin` +The player command is `/ch` (alias `/chunkblock`), the admin command `/chadmin` (aliases `/chunkblockadmin`, `/cba`). Beyond the standard BentoBox island commands: | Command | Description | |---|---| -| `/cb chunks` | Your chunk count, spendable credit, and a chat map of your territory | -| `/cb count` | Magic block count and phase | -| `/cbadmin chunks [reset]` | Inspect a player's chunks and credit, or re-lock them back to the start | -| `/cbadmin bypass` | Toggle chunk-lock enforcement for yourself (needs `chunkblock.mod.bypasschunks`) | +| `/ch chunks` | Your chunk count, spendable credit, and a chat map of your territory | +| `/ch count` | Magic block count and phase | +| `/chadmin chunks [reset]` | Inspect a player's chunks and credit, or re-lock them back to the start | +| `/chadmin bypass` | Toggle chunk-lock enforcement for yourself (needs `chunkblock.mod.bypasschunks`) | ## How claiming works @@ -106,7 +106,7 @@ Lush Caves, Dripstone Caves, Mangrove Swamp, Meadow, Cherry Grove, and Jagged Pe Q: Why can't I walk past the glowing red wall? A: That chunk is still locked! If you're the island owner and have level credit, hit the -wall to claim the chunk. Check `/cb chunks` to see your credit and what's claimable. +wall to claim the chunk. Check `/ch chunks` to see your credit and what's claimable. Q: I lost levels and my farm is behind the wall now. Is it gone? diff --git a/pom.xml b/pom.xml index 6afd64c..d3265e0 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ -LOCAL - 1.0.0 + 1.0.1 BentoBoxWorld_ChunkBlock bentobox-world diff --git a/src/main/java/world/bentobox/chunkblock/ChunkBlock.java b/src/main/java/world/bentobox/chunkblock/ChunkBlock.java index 92f4dde..b2fb86e 100644 --- a/src/main/java/world/bentobox/chunkblock/ChunkBlock.java +++ b/src/main/java/world/bentobox/chunkblock/ChunkBlock.java @@ -43,6 +43,7 @@ import world.bentobox.chunkblock.requests.UnlockedChunksHandler; import world.bentobox.chunkblock.requests.LocationStatsHandler; import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.addons.Addon.State; import world.bentobox.bentobox.api.configuration.Config; import world.bentobox.bentobox.api.configuration.WorldSettings; import world.bentobox.bentobox.api.flags.Flag; @@ -95,7 +96,7 @@ public class ChunkBlock extends GameModeAddon { /** * Flag to enable or disable start safety for players. */ - public final Flag START_SAFETY = new Flag.Builder("START_SAFETY", Material.BAMBOO_BLOCK) + public final Flag CHUNKBLOCK_START_SAFETY = new Flag.Builder("CHUNKBLOCK_START_SAFETY", Material.BAMBOO_BLOCK) .mode(Mode.BASIC) .type(Type.WORLD_SETTING) .listener(new StartSafetyListener(this)) @@ -106,7 +107,7 @@ public class ChunkBlock extends GameModeAddon { /** * Flag to enable or disable the OneBlock boss bar. */ - public final Flag ONEBLOCK_BOSSBAR = new Flag.Builder("ONEBLOCK_BOSSBAR", Material.DRAGON_HEAD) + public final Flag CHUNKBLOCK_BOSSBAR = new Flag.Builder("CHUNKBLOCK_BOSSBAR", Material.DRAGON_HEAD) .mode(Mode.BASIC) .type(Type.SETTING) .listener(bossBar) @@ -115,7 +116,7 @@ public class ChunkBlock extends GameModeAddon { /** * Flag to enable or disable the OneBlock action bar. */ - public final Flag ONEBLOCK_ACTIONBAR = new Flag.Builder("ONEBLOCK_ACTIONBAR", Material.IRON_BARS) + public final Flag CHUNKBLOCK_ACTIONBAR = new Flag.Builder("CHUNKBLOCK_ACTIONBAR", Material.IRON_BARS) .mode(Mode.BASIC) .type(Type.SETTING) .listener(bossBar) @@ -165,14 +166,14 @@ public void onLoad() { adminCommand = new AdminCommand(this); // Register flag with BentoBox // Register protection flag with BentoBox - getPlugin().getFlagsManager().registerFlag(this, START_SAFETY); + getPlugin().getFlagsManager().registerFlag(this, CHUNKBLOCK_START_SAFETY); // Bossbar if (getSettings().isBossBar()) { - getPlugin().getFlagsManager().registerFlag(this, this.ONEBLOCK_BOSSBAR); + getPlugin().getFlagsManager().registerFlag(this, this.CHUNKBLOCK_BOSSBAR); } // Actionbar if (getSettings().isActionBar()) { - getPlugin().getFlagsManager().registerFlag(this, this.ONEBLOCK_ACTIONBAR); + getPlugin().getFlagsManager().registerFlag(this, this.CHUNKBLOCK_ACTIONBAR); } // Magic Block protection getPlugin().getFlagsManager().registerFlag(this, this.MAGIC_BLOCK); @@ -200,6 +201,18 @@ private boolean loadSettings() { @Override public void onEnable() { + // ChunkBlock cannot run without the Level addon: island levels are the currency + // spent to claim chunks. addon.yml declares the hard dependency, but check here + // too so a missing Level shuts the addon down cleanly instead of leaving it + // half-alive. + if (getAddonByName("Level").isEmpty()) { + logError("ChunkBlock requires the Level addon - island levels are the currency used to claim chunks."); + logError("Install Level from https://github.com/BentoBoxWorld/Level or remove ChunkBlock. Disabling."); + // Take down the flags registered in onLoad so their listeners do not linger + getPlugin().getFlagsManager().unregister(this); + setState(State.DISABLED); + return; + } // Initialize the OneBlock manager oneBlockManager = new OneBlocksManager(this); // Initialize the chunk lock manager @@ -226,7 +239,7 @@ public void onEnable() { registerListener(new JoinLeaveListener(this)); registerListener(new InfoListener(this)); // Note: bossBar is registered as a listener by the FlagsManager when the - // ONEBLOCK_BOSSBAR or ONEBLOCK_ACTIONBAR flag is registered in onLoad, so it + // CHUNKBLOCK_BOSSBAR or CHUNKBLOCK_ACTIONBAR flag is registered in onLoad, so it // must not be registered here too or events would be handled twice // Register placeholders phManager = new ChunkBlockPlaceholders(this, getPlugin().getPlaceholdersManager()); diff --git a/src/main/java/world/bentobox/chunkblock/Settings.java b/src/main/java/world/bentobox/chunkblock/Settings.java index 0c47f99..f8fd0ec 100644 --- a/src/main/java/world/bentobox/chunkblock/Settings.java +++ b/src/main/java/world/bentobox/chunkblock/Settings.java @@ -41,12 +41,12 @@ public class Settings implements WorldSettings { @ConfigComment("Player command. What command users will run to access their island.") @ConfigComment("To define alias, just separate commands with white space.") @ConfigEntry(path = "chunkblock.command.island") //, since = "1.3.0") - private String playerCommandAliases = "cb chunkblock"; + private String playerCommandAliases = "ch chunkblock"; @ConfigComment("The admin command.") @ConfigComment("To define alias, just separate commands with white space.") @ConfigEntry(path = "chunkblock.command.admin") // , since = "1.3.0") - private String adminCommandAliases = "cbadmin chunkblockadmin cba"; + private String adminCommandAliases = "chadmin chunkblockadmin cha"; @ConfigComment("The default action for new player command call.") @ConfigComment("Sub-command of main player command that will be run on first player command call.") @@ -269,7 +269,7 @@ public class Settings implements WorldSettings { private int islandDistance = 256; @ConfigComment("Default protection range radius in blocks. Cannot be larger than distance.") - @ConfigComment("Admins can change protection sizes for players individually using /cbadmin range set ") + @ConfigComment("Admins can change protection sizes for players individually using /chadmin range set ") @ConfigComment("or set this permission: chunkblock.island.range.") @ConfigComment("ChunkBlock: this must cover the largest unlockable ring of chunks (see chunkblock.max-chunks).") @ConfigEntry(path = "world.protection-range") @@ -475,7 +475,7 @@ public class Settings implements WorldSettings { private int maxHomes = 5; // Reset - @ConfigComment("How many resets a player is allowed (manage with /cbadmin reset add/remove/reset/set command)") + @ConfigComment("How many resets a player is allowed (manage with /chadmin reset add/remove/reset/set command)") @ConfigComment("Value of -1 means unlimited, 0 means hardcore - no resets.") @ConfigComment("Example, 2 resets means they get 2 resets or 3 islands lifetime") @ConfigEntry(path = "island.reset.reset-limit") @@ -612,7 +612,7 @@ public class Settings implements WorldSettings { @ConfigComment("") @ConfigComment("Here are some examples of valid commands to execute:") @ConfigComment(" * \"[SUDO] bbox version\"") - @ConfigComment(" * \"cbadmin deaths set [player] 0\"") + @ConfigComment(" * \"chadmin deaths set [player] 0\"") @ConfigEntry(path = "island.commands.on-join") // , since = "1.8.0") private List onJoinCommands = new ArrayList<>(); @@ -625,7 +625,7 @@ public class Settings implements WorldSettings { @ConfigComment("") @ConfigComment("Here are some examples of valid commands to execute:") @ConfigComment(" * '[SUDO] bbox version'") - @ConfigComment(" * 'cbadmin deaths set [player] 0'") + @ConfigComment(" * 'chadmin deaths set [player] 0'") @ConfigComment("") @ConfigComment("Note that player-executed commands might not work, as these commands can be run with said player being offline.") @ConfigEntry(path = "island.commands.on-leave") // , since = "1.8.0") @@ -640,7 +640,7 @@ public class Settings implements WorldSettings { @ConfigComment("") @ConfigComment("Here are some examples of valid commands to execute:") @ConfigComment(" * '[SUDO] bbox version'") - @ConfigComment(" * 'cbadmin deaths set [player] 0'") + @ConfigComment(" * 'chadmin deaths set [player] 0'") @ConfigComment("") @ConfigComment("Note that player-executed commands might not work, as these commands can be run with said player being offline.") @ConfigEntry(path = "island.commands.on-respawn") // , since = "1.14.0") diff --git a/src/main/java/world/bentobox/chunkblock/commands/admin/AdminBypassCommand.java b/src/main/java/world/bentobox/chunkblock/commands/admin/AdminBypassCommand.java index 4d00094..b72e477 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/admin/AdminBypassCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/admin/AdminBypassCommand.java @@ -7,7 +7,7 @@ import world.bentobox.chunkblock.ChunkBlock; /** - * /cbadmin bypass — lets staff with the bypass permission toggle chunk lock enforcement + * /chadmin bypass — lets staff with the bypass permission toggle chunk lock enforcement * for themselves, so they can test the game as players see it and inspect cleanly. * * @author tastybento diff --git a/src/main/java/world/bentobox/chunkblock/commands/admin/AdminChunksCommand.java b/src/main/java/world/bentobox/chunkblock/commands/admin/AdminChunksCommand.java index 394aa20..4048249 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/admin/AdminChunksCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/admin/AdminChunksCommand.java @@ -14,7 +14,7 @@ import world.bentobox.chunkblock.chunks.ChunkManager; /** - * /cbadmin chunks <player> [reset] — support and debug tool: shows a player's + * /chadmin chunks <player> [reset] — support and debug tool: shows a player's * unlocked chunks, spending and credit, or re-locks everything back to the center chunk. * * @author tastybento diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandActionBarCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandActionBarCommand.java index c06942a..4b645ae 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandActionBarCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandActionBarCommand.java @@ -29,13 +29,13 @@ public void setup() { @Override public boolean execute(User user, String label, List args) { getIslands().getIslandAt(user.getLocation()).ifPresent(i -> { - if (!i.isAllowed(addon.ONEBLOCK_ACTIONBAR)) { + if (!i.isAllowed(addon.CHUNKBLOCK_ACTIONBAR)) { user.sendMessage("chunkblock.actionbar.not-active"); } }); // Toggle state - boolean newState = !user.getMetaData(BossBarListener.AONEBLOCK_ACTIONBAR).map(MetaDataValue::asBoolean).orElse(true); - user.putMetaData(BossBarListener.AONEBLOCK_ACTIONBAR, new MetaDataValue(newState)); + boolean newState = !user.getMetaData(BossBarListener.ACTIONBAR_METADATA).map(MetaDataValue::asBoolean).orElse(true); + user.putMetaData(BossBarListener.ACTIONBAR_METADATA, new MetaDataValue(newState)); if (newState) { user.sendMessage("chunkblock.commands.island.actionbar.status_on"); } else { diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandBossBarCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandBossBarCommand.java index 8ed54fc..87d0469 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandBossBarCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandBossBarCommand.java @@ -28,7 +28,7 @@ public void setup() { public boolean execute(User user, String label, List args) { addon.getBossBar().toggleUser(user); getIslands().getIslandAt(user.getLocation()).ifPresent(i -> { - if (!i.isAllowed(addon.ONEBLOCK_BOSSBAR)) { + if (!i.isAllowed(addon.CHUNKBLOCK_BOSSBAR)) { user.sendMessage("chunkblock.bossbar.not-active"); } }); diff --git a/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java b/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java index 09b7fcf..d1fa81b 100644 --- a/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java +++ b/src/main/java/world/bentobox/chunkblock/commands/island/IslandChunksCommand.java @@ -13,7 +13,7 @@ import world.bentobox.chunkblock.chunks.ChunkManager.ClaimResult; /** - * /cb chunks — shows how big your island is, how much level credit you can spend, and a + * /ch chunks — shows how big your island is, how much level credit you can spend, and a * little chat map of your territory with the chunks you could claim next. * * @author tastybento diff --git a/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java b/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java index f9d1765..8ad2450 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/BossBarListener.java @@ -32,8 +32,8 @@ public class BossBarListener implements Listener { - private static final String AONEBLOCK_BOSSBAR = "chunkblock.bossbar"; - public static final String AONEBLOCK_ACTIONBAR = "chunkblock.actionbar"; + private static final String BOSSBAR_METADATA = "chunkblock.bossbar"; + public static final String ACTIONBAR_METADATA = "chunkblock.actionbar"; private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.builder() .character('&') @@ -50,8 +50,21 @@ public BossBarListener(ChunkBlock addon) { // Store a boss bar for each player (using their UUID) private final Map islandBossBars = new HashMap<>(); + /** + * This listener is registered with the flags in onLoad, so it can outlive an addon + * that never enabled (e.g. missing dependency) and therefore has no worlds. Every + * event handler must bail out via this check before touching addon state. + * @return true if the addon is running with its world available + */ + private boolean ready() { + return addon.getOverWorld() != null; + } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onBreakBlockEvent(MagicBlockEvent e) { + if (!ready()) { + return; + } // Update boss bar tryToShowBossBar(e.getPlayerUUID(), e.getIsland()); tryToShowActionBar(e.getPlayerUUID(), e.getIsland()); @@ -59,7 +72,7 @@ public void onBreakBlockEvent(MagicBlockEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onEnterIsland(IslandEnterEvent event) { - if (addon.inWorld(event.getIsland().getWorld())) { + if (ready() && addon.inWorld(event.getIsland().getWorld())) { tryToShowBossBar(event.getPlayerUUID(), event.getIsland()); tryToShowActionBar(event.getPlayerUUID(), event.getIsland()); } @@ -67,7 +80,10 @@ public void onEnterIsland(IslandEnterEvent event) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onFlagChange(FlagSettingChangeEvent e) { - if (e.getEditedFlag() == addon.ONEBLOCK_BOSSBAR) { + if (!ready()) { + return; + } + if (e.getEditedFlag() == addon.CHUNKBLOCK_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 -> { @@ -100,11 +116,11 @@ private void tryToShowActionBar(UUID uuid, Island island) { Player player = Bukkit.getPlayer(uuid); // Only show if enabled for island - if (!island.isAllowed(addon.ONEBLOCK_ACTIONBAR)) { + if (!island.isAllowed(addon.CHUNKBLOCK_ACTIONBAR)) { return; } // Default to showing action bar unless it is explicitly turned off - if (!user.getMetaData(AONEBLOCK_ACTIONBAR).map(MetaDataValue::asBoolean).orElse(true)) { + if (!user.getMetaData(ACTIONBAR_METADATA).map(MetaDataValue::asBoolean).orElse(true)) { // Do not show an action bar return; } @@ -138,7 +154,7 @@ private void tryToShowBossBar(UUID uuid, Island island) { User user = User.getInstance(uuid); // Only show if enabled for island - if (!island.isAllowed(addon.ONEBLOCK_BOSSBAR)) { + if (!island.isAllowed(addon.CHUNKBLOCK_BOSSBAR)) { BossBar removed = islandBossBars.remove(island); if (removed != null) { // Remove all players from the boss bar @@ -147,7 +163,7 @@ private void tryToShowBossBar(UUID uuid, Island island) { return; } // Default to showing boss bar unless it is explicitly turned off - if (!user.getMetaData(AONEBLOCK_BOSSBAR).map(MetaDataValue::asBoolean).orElse(true)) { + if (!user.getMetaData(BOSSBAR_METADATA).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 @@ -213,6 +229,9 @@ private void removeBar(User user, Island island) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onExitIsland(IslandExitEvent event) { + if (!ready()) { + return; + } User user = User.getInstance(event.getPlayerUUID()); removeBar(user, event.getIsland()); } @@ -221,7 +240,7 @@ public void onExitIsland(IslandExitEvent event) { public void onJoin(PlayerJoinEvent e) { // If the player is on an island then show the bar Location playerLoc = e.getPlayer().getLocation(); - if (playerLoc == null || !addon.inWorld(playerLoc)) { + if (!ready() || playerLoc == null || !addon.inWorld(playerLoc)) { return; } addon.getIslands().getIslandAt(playerLoc) @@ -240,8 +259,8 @@ public void onQuit(PlayerQuitEvent e) { * @param user user to toggle */ public void toggleUser(User user) { - boolean newState = !user.getMetaData(AONEBLOCK_BOSSBAR).map(MetaDataValue::asBoolean).orElse(true); - user.putMetaData(AONEBLOCK_BOSSBAR, new MetaDataValue(newState)); + boolean newState = !user.getMetaData(BOSSBAR_METADATA).map(MetaDataValue::asBoolean).orElse(true); + user.putMetaData(BOSSBAR_METADATA, new MetaDataValue(newState)); if (newState) { // If the player is on an island then show the bar addon.getIslands().getIslandAt(user.getLocation()).filter(is -> addon.inWorld(is.getWorld())) diff --git a/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java b/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java index cd49d6a..e273059 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/ChunkClaimListener.java @@ -1,9 +1,13 @@ package world.bentobox.chunkblock.listeners; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; +import java.util.UUID; import org.bukkit.Location; import org.bukkit.Sound; +import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -32,8 +36,11 @@ public class ChunkClaimListener implements Listener { private static final double REACH = 5.0; /** Ray step size in blocks */ private static final double STEP = 0.25; + /** Minimum time between failure nags per player, so mining swings can't spam */ + private static final long FEEDBACK_COOLDOWN_MS = 2000; private final ChunkBlock addon; + private final Map lastFeedback = new HashMap<>(); public ChunkClaimListener(ChunkBlock addon) { this.addon = addon; @@ -69,7 +76,20 @@ public void onBorderHit(PlayerInteractEvent e) { if (cm.isLocked(island, player.getLocation())) { return; } - int[] target = findTargetLockedChunk(player, island); + // A click on a block inside unlocked territory is ordinary interaction (mining a + // generator, pressing a button...), never a claim gesture — regardless of where + // the aim line would end up beyond it. + Block clicked = e.getClickedBlock(); + if (clicked != null && cm.isUnlocked(island, clicked.getX() >> 4, clicked.getZ() >> 4)) { + return; + } + int[] target; + if (clicked != null) { + // The clicked block is itself in a locked chunk: that chunk is the target + target = new int[] { clicked.getX() >> 4, clicked.getZ() >> 4 }; + } else { + target = findTargetLockedChunk(player, island); + } if (target == null) { return; } @@ -91,12 +111,17 @@ private int[] findTargetLockedChunk(Player player, Island island) { } ChunkManager cm = addon.getChunkManager(); for (double d = STEP; d <= REACH; d += STEP) { - Location point = eye.clone().add(direction.getX() * d, 0, direction.getZ() * d); + Location point = eye.clone().add(direction.getX() * d, direction.getY() * d, direction.getZ() * d); int chunkX = point.getBlockX() >> 4; int chunkZ = point.getBlockZ() >> 4; if (!cm.isUnlocked(island, chunkX, chunkZ)) { return new int[] { chunkX, chunkZ }; } + Block block = point.getBlock(); + if (block != null && !block.isPassable()) { + // The aim line is blocked by the player's own blocks before the border + return null; + } } return null; } @@ -115,14 +140,34 @@ public void attemptClaim(User user, Island island, int chunkX, int chunkZ) { switch (result) { case OK -> addon.getLevelListener().celebrateClaim(island, chunkX, chunkZ); case NO_CREDIT -> { - long needed = cm.getChunkCost() - cm.getCredit(island); - user.notify("chunkblock.chunks.no-credit", "[needed]", String.valueOf(needed)); - user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1F, 0.6F); + if (feedbackReady(user.getUniqueId())) { + long needed = cm.getChunkCost() - cm.getCredit(island); + user.notify("chunkblock.chunks.no-credit", "[needed]", String.valueOf(needed)); + user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1F, 0.6F); + } + } + case BEYOND_LIMIT -> { + if (feedbackReady(user.getUniqueId())) { + user.notify("chunkblock.chunks.beyond-limit"); + } } - case BEYOND_LIMIT -> user.notify("chunkblock.chunks.beyond-limit"); case NOT_ADJACENT, ALREADY_UNLOCKED -> { // Aiming at a diagonal corner or a chunk already owned: no claim, no nag } } } + + /** + * Rate-limits failure feedback: repeated swings while mining should not turn every + * failed claim probe into a chat message and a sound. + */ + private boolean feedbackReady(UUID uuid) { + long now = System.currentTimeMillis(); + Long last = lastFeedback.get(uuid); + if (last != null && now - last < FEEDBACK_COOLDOWN_MS) { + return false; + } + lastFeedback.put(uuid, now); + return true; + } } diff --git a/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java b/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java index 205611d..750d4df 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/ChunkGuardListener.java @@ -41,6 +41,7 @@ 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; import world.bentobox.chunkblock.ChunkBlock; import world.bentobox.chunkblock.chunks.ChunkManager; @@ -185,8 +186,12 @@ public void backtrack(Player player) { if (optionalIsland.isEmpty()) { optionalIsland = Optional.ofNullable(addon.getIslands().getIsland(loc.getWorld(), User.getInstance(player))); } - if (optionalIsland.isEmpty()) { - // Not on any island grid and no island of their own: home teleport is all we can do + // Only relocate a player within an island they actually belong to. Anyone else + // (e.g. a respawn that landed on a stranger's or abandoned island) goes to their + // own island home instead — never dropped, with a landing block, onto an island + // that is not theirs. + if (optionalIsland.isEmpty() + || !optionalIsland.get().getMemberSet(RanksManager.COOP_RANK).contains(player.getUniqueId())) { addon.getIslands().homeTeleportAsync(Objects.requireNonNull(loc.getWorld()), player); return; } diff --git a/src/main/java/world/bentobox/chunkblock/listeners/StartSafetyListener.java b/src/main/java/world/bentobox/chunkblock/listeners/StartSafetyListener.java index 69870d1..54bfef4 100644 --- a/src/main/java/world/bentobox/chunkblock/listeners/StartSafetyListener.java +++ b/src/main/java/world/bentobox/chunkblock/listeners/StartSafetyListener.java @@ -37,7 +37,12 @@ public void onNewIsland(IslandCreatedEvent e) { } private void store(World world, UUID playerUUID) { - if (addon.inWorld(world) && addon.START_SAFETY.isSetForWorld(world) && !newIslands.containsKey(playerUUID)) { + // This listener is registered with the CHUNKBLOCK_START_SAFETY flag in onLoad, so it can + // outlive an addon that never enabled (e.g. missing dependency) and has no world + if (addon.getOverWorld() == null) { + return; + } + if (addon.inWorld(world) && addon.CHUNKBLOCK_START_SAFETY.isSetForWorld(world) && !newIslands.containsKey(playerUUID)) { long time = addon.getSettings().getStartingSafetyDuration(); if (time < 0) { time = 10; // 10 seconds @@ -45,7 +50,7 @@ private void store(World world, UUID playerUUID) { newIslands.put(playerUUID, System.currentTimeMillis() + (time * 1000)); Bukkit.getScheduler().runTaskLater(addon.getPlugin(), () -> { newIslands.remove(playerUUID); - User.getInstance(playerUUID).sendMessage("protection.flags.START_SAFETY.free-to-move"); + User.getInstance(playerUUID).sendMessage("protection.flags.CHUNKBLOCK_START_SAFETY.free-to-move"); }, time); } @@ -58,7 +63,8 @@ public void onResetIsland(IslandResetEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onPlayerMove(PlayerMoveEvent e) { - if (addon.inWorld(e.getPlayer().getWorld()) && newIslands.containsKey(e.getPlayer().getUniqueId()) + if (addon.getOverWorld() != null && addon.inWorld(e.getPlayer().getWorld()) + && newIslands.containsKey(e.getPlayer().getUniqueId()) && !e.getPlayer().isSneaking() && (e.getFrom().getX() != e.getTo().getX() || e.getFrom().getZ() != e.getTo().getZ())) { // Do not allow x or z movement @@ -66,7 +72,7 @@ public void onPlayerMove(PlayerMoveEvent e) { e.getTo().getYaw(), e.getTo().getPitch())); String waitTime = String .valueOf((int) ((newIslands.get(e.getPlayer().getUniqueId()) - System.currentTimeMillis()) / 1000)); - User.getInstance(e.getPlayer()).notify(addon.START_SAFETY.getHintReference(), TextVariables.NUMBER, + User.getInstance(e.getPlayer()).notify(addon.CHUNKBLOCK_START_SAFETY.getHintReference(), TextVariables.NUMBER, waitTime); } } diff --git a/src/main/java/world/bentobox/chunkblock/panels/PhasesPanel.java b/src/main/java/world/bentobox/chunkblock/panels/PhasesPanel.java index 88dfeb8..394cd68 100644 --- a/src/main/java/world/bentobox/chunkblock/panels/PhasesPanel.java +++ b/src/main/java/world/bentobox/chunkblock/panels/PhasesPanel.java @@ -539,6 +539,10 @@ private boolean canApplyPhase(OneBlockPhase phase) { return false; } + if (!this.hasSetCountPermission()) + { + return false; + } if (phase.getBlockNumberValue() >= this.oneBlockIsland.getLifetime()) { return false; @@ -546,6 +550,19 @@ private boolean canApplyPhase(OneBlockPhase phase) return !this.phaseRequirementsFail(phase, this.oneBlockIsland); } + /** + * Checks that the player holds the permission of the setcount command that a phase + * click runs, so the "click to change" action is only offered when it can succeed. + */ + private boolean hasSetCountPermission() + { + String label = this.addon.getSettings().getSetCountCommand().split(" ")[0]; + return this.addon.getPlayerCommand() + .flatMap(mainCommand -> mainCommand.getSubCommand(label)) + .map(subCommand -> this.user.hasPermission(subCommand.getPermission())) + .orElse(true); + } + /** * Builds the click handler for a phase button. */ diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index e172aa4..257373a 100755 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -40,7 +40,7 @@ permissions: description: Bypasses an island lock default: op chunkblock.mod.bypasschunks: - description: Exempts the holder from chunk locking entirely; also allows '/cbadmin bypass' to toggle it. Not given to ops by default - it must be granted explicitly so staff play by the same rules until they opt in. + description: Exempts the holder from chunk locking entirely; also allows '/chadmin bypass' to toggle it. Not given to ops by default - it must be granted explicitly so staff play by the same rules until they opt in. default: false chunkblock.mod.bypassban: description: Bypasses island ban @@ -53,284 +53,284 @@ permissions: default: op # Command permissions chunkblock.island: - description: Allow use of '/cb' command - the main island command + description: Allow use of '/ch' command - the main island command default: TRUE chunkblock.island.actionbar: - description: Allow use of '/cb actionbar' command - toggle the actionbar + description: Allow use of '/ch actionbar' command - toggle the actionbar default: TRUE chunkblock.island.bossbar: - description: Allow use of '/cb bossbar' command - toggle the bossbar + description: Allow use of '/ch bossbar' command - toggle the bossbar default: TRUE chunkblock.island.home: - description: Allow use of '/cb go' command - teleport you to your island + description: Allow use of '/ch go' command - teleport you to your island default: TRUE chunkblock.island.spawn: - description: Allow use of '/cb spawn' command - teleport you to the spawn + description: Allow use of '/ch spawn' command - teleport you to the spawn default: TRUE chunkblock.island.create: - description: Allow use of '/cb create' command - create an island, using optional blueprint (requires permission) + description: Allow use of '/ch create' command - create an island, using optional blueprint (requires permission) default: TRUE chunkblock.island.reset: - description: Allow use of '/cb reset' command - restart your island and remove the old one + description: Allow use of '/ch reset' command - restart your island and remove the old one default: TRUE chunkblock.island.info: - description: Allow use of '/cb info' command - display info about your island or the player's island + description: Allow use of '/ch info' command - display info about your island or the player's island default: TRUE chunkblock.island.settings: - description: Allow use of '/cb settings' command - display island settings + description: Allow use of '/ch settings' command - display island settings default: TRUE chunkblock.island.name: - description: Allow use of '/cb setname' or '/cb resetname' command - your island name + description: Allow use of '/ch setname' or '/ch resetname' command - your island name default: TRUE chunkblock.island.language: - description: Allow use of '/cb language' command - select language + description: Allow use of '/ch language' command - select language default: TRUE chunkblock.island.ban: - description: Allow use of '/cb ban' or '/cb unban' or '/cb banlist' command - banned players + description: Allow use of '/ch ban' or '/ch unban' or '/ch banlist' command - banned players default: TRUE chunkblock.island.expel: - description: Allow use of '/cb expel' command - expel a player from your island + description: Allow use of '/ch expel' command - expel a player from your island default: TRUE chunkblock.island.near: - description: Allow use of '/cb near' command - show the name of neighboring islands around you + description: Allow use of '/ch near' command - show the name of neighboring islands around you default: TRUE chunkblock.island.team: - description: Allow use of '/cb team' command - manage your team + description: Allow use of '/ch team' command - manage your team default: TRUE chunkblock.island.team.invite: - description: Allow use of '/cb team invite' command - invite a player to join your island + description: Allow use of '/ch team invite' command - invite a player to join your island default: TRUE chunkblock.island.team.leave: - description: Allow use of '/cb team leave' command - leave your island + description: Allow use of '/ch team leave' command - leave your island default: TRUE chunkblock.island.team.setowner: - description: Allow use of '/cb team setowner' command - transfer your island ownership to a member + description: Allow use of '/ch team setowner' command - transfer your island ownership to a member default: TRUE chunkblock.island.team.kick: - description: Allow use of '/cb team kick' command - remove a member from your island + description: Allow use of '/ch team kick' command - remove a member from your island default: TRUE chunkblock.island.team.accept: - description: Allow use of '/cb team accept' command - accept an invitation + description: Allow use of '/ch team accept' command - accept an invitation default: TRUE chunkblock.island.team.reject: - description: Allow use of '/cb team reject' command - reject an invitation + description: Allow use of '/ch team reject' command - reject an invitation default: TRUE chunkblock.island.team.coop: - description: Allow use of '/cb team coop, uncoop' commands + description: Allow use of '/ch team coop, uncoop' commands default: TRUE chunkblock.island.team.trust: - description: Allow use of '/cb team trust, untrust' commands + description: Allow use of '/ch team trust, untrust' commands default: TRUE chunkblock.island.team.promote: - description: Allow use of '/cb team promote, demote' command + description: Allow use of '/ch team promote, demote' command default: TRUE chunkblock.island.sethome: - description: Allow use of '/cb sethome' command - set your home teleport point + description: Allow use of '/ch sethome' command - set your home teleport point default: TRUE chunkblock.island.deletehome: - description: Allow use of '/cb deletehome' command - delete a home location + description: Allow use of '/ch deletehome' command - delete a home location default: OP chunkblock.island.renamehome: - description: Allow use of '/cb renamehome' command - rename a home location + description: Allow use of '/ch renamehome' command - rename a home location default: OP chunkblock.island.homes: - description: Allow use of '/cb homes' command - list your homes + description: Allow use of '/ch homes' command - list your homes default: OP chunkblock.count: - description: Allow use of '/cb count' command - show the block count and phase + description: Allow use of '/ch count' command - show the block count and phase default: TRUE chunkblock.phases: - description: Allow use of '/cb phases' command - show a list of all the phases + description: Allow use of '/ch phases' command - show a list of all the phases default: FALSE chunkblock.island.setcount: - description: Allow use of '/cb setCount' command - set block count to previously completed value + description: Allow use of '/ch setCount' command - set block count to previously completed value default: OP chunkblock.respawn-block: - description: Allow use of '/cb respawnBlock' command - respawns magic block in situations when they disappear + description: Allow use of '/ch respawnBlock' command - respawns magic block in situations when they disappear default: TRUE chunkblock.admin: - description: Allow use of '/cbadmin' command - admin command + description: Allow use of '/chadmin' command - admin command default: OP chunkblock.admin.version: - description: Allow use of '/cbadmin version' command - display BentoBox and addons versions + description: Allow use of '/chadmin version' command - display BentoBox and addons versions default: OP chunkblock.admin.tp: - description: Allow use of '/cbadmin tp/tpnether/tpend' command - teleport to a player's island + description: Allow use of '/chadmin tp/tpnether/tpend' command - teleport to a player's island default: OP chunkblock.admin.getrank: - description: Allow use of '/cbadmin getrank' command - get a player's rank on their island or the island of the owner + description: Allow use of '/chadmin getrank' command - get a player's rank on their island or the island of the owner default: OP chunkblock.admin.setrank: - description: Allow use of '/cbadmin setrank' command - set a player's rank on their island or the island of the owner + description: Allow use of '/chadmin setrank' command - set a player's rank on their island or the island of the owner default: OP chunkblock.mod.info: - description: Allow use of '/cbadmin info' command - get info on where you are or player's island + description: Allow use of '/chadmin info' command - get info on where you are or player's island default: OP chunkblock.mod.team: - description: Allow use of '/cbadmin team' command - manage teams + description: Allow use of '/chadmin team' command - manage teams default: FALSE chunkblock.mod.team.add: - description: Allow use of '/cbadmin team add' or '/cbadmin add' command - add player to owner's team + description: Allow use of '/chadmin team add' or '/chadmin add' command - add player to owner's team default: OP chunkblock.mod.team.disband: - description: Allow use of '/cbadmin team disband' or '/cbadmin disband' command - disband owner's team + description: Allow use of '/chadmin team disband' or '/chadmin disband' command - disband owner's team default: OP chunkblock.mod.team.fix: - description: Allow use of '/cbadmin team fix' or '/cbadmin fix' command - scans and fixes cross island membership in database + description: Allow use of '/chadmin team fix' or '/chadmin fix' command - scans and fixes cross island membership in database default: OP chunkblock.mod.team.kick: - description: Allow use of '/cbadmin team kick' or '/cbadmin kick' command - kick a player from a team + description: Allow use of '/chadmin team kick' or '/chadmin kick' command - kick a player from a team default: OP chunkblock.mod.team.setowner: - description: Allow use of '/cbadmin team setowner' command - transfers island ownership to the player + description: Allow use of '/chadmin team setowner' command - transfers island ownership to the player default: OP chunkblock.admin.blueprint: - description: Allow use of '/cbadmin blueprint' command - manipulate blueprints + description: Allow use of '/chadmin blueprint' command - manipulate blueprints default: OP chunkblock.admin.blueprint.load: - description: Allow use of '/cbadmin blueprint load' command - load blueprint into the clipboard + description: Allow use of '/chadmin blueprint load' command - load blueprint into the clipboard default: OP chunkblock.admin.blueprint.paste: - description: Allow use of '/cbadmin blueprint paste' command - paste the clipboard to your location + description: Allow use of '/chadmin blueprint paste' command - paste the clipboard to your location default: OP chunkblock.admin.blueprint.origin: - description: Allow use of '/cbadmin blueprint origin' command - set the blueprint's origin to your position + description: Allow use of '/chadmin blueprint origin' command - set the blueprint's origin to your position default: OP chunkblock.admin.blueprint.copy: - description: Allow use of '/cbadmin blueprint copy' command - copy the clipboard set by pos1 and pos2 and optionally the air blocks + description: Allow use of '/chadmin blueprint copy' command - copy the clipboard set by pos1 and pos2 and optionally the air blocks default: OP chunkblock.admin.blueprint.save: - description: Allow use of '/cbadmin blueprint save' command - save the copied clipboard + description: Allow use of '/chadmin blueprint save' command - save the copied clipboard default: OP chunkblock.admin.blueprint.rename: - description: Allow use of '/cbadmin blueprint rename' command - rename a blueprint + description: Allow use of '/chadmin blueprint rename' command - rename a blueprint default: OP chunkblock.admin.blueprint.delete: - description: Allow use of '/cbadmin blueprint delete' command - delete the blueprint + description: Allow use of '/chadmin blueprint delete' command - delete the blueprint default: OP chunkblock.admin.blueprint.pos1: - description: Allow use of '/cbadmin blueprint pos1' command - set 1st corner of cuboid clipboard + description: Allow use of '/chadmin blueprint pos1' command - set 1st corner of cuboid clipboard default: OP chunkblock.admin.blueprint.pos2: - description: Allow use of '/cbadmin blueprint pos2' command - set 2nd corner of cuboid clipboard + description: Allow use of '/chadmin blueprint pos2' command - set 2nd corner of cuboid clipboard default: OP chunkblock.admin.blueprint.list: - description: Allow use of '/cbadmin blueprint list' command - list available blueprints + description: Allow use of '/chadmin blueprint list' command - list available blueprints default: OP chunkblock.admin.register: - description: Allow use of '/cbadmin register' command - register player to unowned island you are on + description: Allow use of '/chadmin register' command - register player to unowned island you are on default: OP chunkblock.admin.unregister: - description: Allow use of '/cbadmin unregister' command - unregister owner from island, but keep island blocks + description: Allow use of '/chadmin unregister' command - unregister owner from island, but keep island blocks default: OP chunkblock.admin.range: - description: Allow use of '/cbadmin range' command - admin island range command + description: Allow use of '/chadmin range' command - admin island range command default: OP chunkblock.admin.range.display: - description: Allow use of '/cbadmin range display' command - show/hide island range indicators + description: Allow use of '/chadmin range display' command - show/hide island range indicators default: OP chunkblock.admin.range.set: - description: Allow use of '/cbadmin range set' command - sets the island protected range + description: Allow use of '/chadmin range set' command - sets the island protected range default: OP chunkblock.admin.range.reset: - description: Allow use of '/cbadmin range reset' command - resets the island protected range to the world default + description: Allow use of '/chadmin range reset' command - resets the island protected range to the world default default: OP chunkblock.admin.range.add: - description: Allow use of '/cbadmin range add' command - increases the island protected range + description: Allow use of '/chadmin range add' command - increases the island protected range default: OP chunkblock.admin.range.remove: - description: Allow use of '/cbadmin range remove' command - decreases the island protected range + description: Allow use of '/chadmin range remove' command - decreases the island protected range default: OP chunkblock.admin.resets: - description: Allow use of '/cbadmin resets' command - edit player reset values + description: Allow use of '/chadmin resets' command - edit player reset values default: OP chunkblock.admin.resets.set: - description: Allow use of '/cbadmin resets set' command - sets how many times this player has reset his island + description: Allow use of '/chadmin resets set' command - sets how many times this player has reset his island default: OP chunkblock.admin.resets.add: - description: Allow use of '/cbadmin resets add' command - adds this player's island reset count + description: Allow use of '/chadmin resets add' command - adds this player's island reset count default: OP chunkblock.admin.resets.remove: - description: Allow use of '/cbadmin resets remove' command - reduces the player's island reset count + description: Allow use of '/chadmin resets remove' command - reduces the player's island reset count default: OP chunkblock.admin.delete: - description: Allow use of '/cbadmin delete' command - deletes a player's island + description: Allow use of '/chadmin delete' command - deletes a player's island default: OP chunkblock.admin.why: - description: Allow use of '/cbadmin why' command - toggle console protection debug reporting + description: Allow use of '/chadmin why' command - toggle console protection debug reporting default: OP chunkblock.admin.deaths: - description: Allow use of '/cbadmin deaths' command - edit deaths of players + description: Allow use of '/chadmin deaths' command - edit deaths of players default: OP chunkblock.admin.deaths.reset: - description: Allow use of '/cbadmin deaths reset' command - resets deaths of the player + description: Allow use of '/chadmin deaths reset' command - resets deaths of the player default: OP chunkblock.admin.deaths.set: - description: Allow use of '/cbadmin deaths set' command - sets deaths of the player + description: Allow use of '/chadmin deaths set' command - sets deaths of the player default: OP chunkblock.admin.deaths.add: - description: Allow use of '/cbadmin deaths add' command - adds deaths to the player + description: Allow use of '/chadmin deaths add' command - adds deaths to the player default: OP chunkblock.admin.deaths.remove: - description: Allow use of '/cbadmin deaths remove' command - removes deaths to the player + description: Allow use of '/chadmin deaths remove' command - removes deaths to the player default: OP chunkblock.admin.reload: - description: Allow use of '/cbadmin reload' command - reload + description: Allow use of '/chadmin reload' command - reload default: OP chunkblock.admin.setspawn: - description: Allow use of '/cbadmin setspawn' command - set an island as spawn for this gamemode + description: Allow use of '/chadmin setspawn' command - set an island as spawn for this gamemode default: OP chunkblock.admin.setspawnpoint: - description: Allow use of '/cbadmin setspawnpoint' command - set current location as spawn point for this island + description: Allow use of '/chadmin setspawnpoint' command - set current location as spawn point for this island default: OP chunkblock.admin.resetflags: - description: Allow use of '/cbadmin resetflags' command - Reset all islands to default flag settings in config.yml + description: Allow use of '/chadmin resetflags' command - Reset all islands to default flag settings in config.yml default: OP chunkblock.mod.switch: - description: Allow use of '/cbadmin switch' command - switch on/off protection bypass + description: Allow use of '/chadmin switch' command - switch on/off protection bypass default: OP chunkblock.admin.purge: - description: Allow use of '/cbadmin purge' command - purge islands abandoned for more than [days] + description: Allow use of '/chadmin purge' command - purge islands abandoned for more than [days] default: OP chunkblock.admin.purge.status: - description: Allow use of '/cbadmin purge status' command - displays the status of the purge + description: Allow use of '/chadmin purge status' command - displays the status of the purge default: OP chunkblock.admin.purge.stop: - description: Allow use of '/cbadmin purge stop' command - stop a purge in progress + description: Allow use of '/chadmin purge stop' command - stop a purge in progress default: OP chunkblock.admin.purge.unowned: - description: Allow use of '/cbadmin purge unowned' command - purge unowned islands + description: Allow use of '/chadmin purge unowned' command - purge unowned islands default: OP chunkblock.admin.purge.protect: - description: Allow use of '/cbadmin purge protect' command - toggle island purge protection + description: Allow use of '/chadmin purge protect' command - toggle island purge protection default: OP chunkblock.admin.settings: - description: Allow use of '/cbadmin settings' command - open settings GUI or set settings + description: Allow use of '/chadmin settings' command - open settings GUI or set settings default: OP chunkblock.admin.setprotectionlocation: - description: Allow use of '/cbadmin setprotectionlocation' command - set current location or [x y z] as center of island's protection area + description: Allow use of '/chadmin setprotectionlocation' command - set current location or [x y z] as center of island's protection area default: OP chunkblock.mod.deletehomes: - description: Allow use of '/cbadmin deletehomes' command - deletes all named homes from an island + description: Allow use of '/chadmin deletehomes' command - deletes all named homes from an island default: OP chunkblock.mod.resetname: - description: Allow use of '/cbadmin resetname' command - reset player island name + description: Allow use of '/chadmin resetname' command - reset player island name default: OP chunkblock.admin.setcount: - description: Allow use of '/cbadmin setcount' command - set player's block count + description: Allow use of '/chadmin setcount' command - set player's block count default: OP chunkblock.admin.setchest: - description: Allow use of '/cbadmin setchest' command - put the looked-at chest in a phase with the rarity specified + description: Allow use of '/chadmin setchest' command - put the looked-at chest in a phase with the rarity specified default: OP chunkblock.admin.sanity: - description: Allow use of '/cbadmin sanity' command - display a sanity check of the phase probabilities in the console + description: Allow use of '/chadmin sanity' command - display a sanity check of the phase probabilities in the console default: OP chunkblock.admin.phases: - description: Allow use of '/cbadmin phases' command - open the phase order editor + description: Allow use of '/chadmin phases' command - open the phase order editor default: OP chunkblock.island.chunks: - description: Allow use of '/cb chunks' command - show your unlocked chunks and territory map + description: Allow use of '/ch chunks' command - show your unlocked chunks and territory map default: TRUE chunkblock.admin.chunks: - description: Allow use of '/cbadmin chunks' command - inspect, set or recalculate a player's unlocked chunks + description: Allow use of '/chadmin chunks' command - inspect, set or recalculate a player's unlocked chunks default: OP diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 4024d63..3897aa6 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -3,10 +3,10 @@ chunkblock: command: # Player command. What command users will run to access their island. # To define alias, just separate commands with white space. - island: cb chunkblock + island: ch chunkblock # The admin command. # To define alias, just separate commands with white space. - admin: cbadmin chunkblockadmin cba + admin: chadmin chunkblockadmin cha # The default action for new player command call. # Sub-command of main player command that will be run on first player command call. # By default, it is sub-command 'create'. @@ -174,7 +174,7 @@ world: # Values that are not a multiple of 8 are snapped to the nearest multiple on load. distance-between-islands: 256 # Default protection range radius in blocks. Cannot be larger than distance. - # Admins can change protection sizes for players individually using /cbadmin range set + # Admins can change protection sizes for players individually using /chadmin range set # or set this permission: chunkblock.island.range. # ChunkBlock: this must cover the largest unlockable ring of chunks (see chunkblock.max-chunks). protection-range: 240 @@ -290,7 +290,7 @@ world: CLEAN_SUPER_FLAT: false CHEST_DAMAGE: false PREVENT_TELEPORT_WHEN_FALLING: false - START_SAFETY: false + CHUNKBLOCK_START_SAFETY: false ENTER_EXIT_MESSAGES: true ALLOW_MOVE_BOX: true ENDERMAN_DEATH_DROP: true @@ -421,7 +421,7 @@ world: TNT_DAMAGE: true MONSTER_SPAWNERS_SPAWN: true FIRE_IGNITE: true - ONEBLOCK_BOSSBAR: true + CHUNKBLOCK_BOSSBAR: true BLOCK_EXPLODE_DAMAGE: true ANIMAL_SPAWNERS_SPAWN: true # These settings/flags are hidden from users @@ -461,7 +461,7 @@ island: # Accessed via /is sethome or /is go max-homes: 1 reset: - # How many resets a player is allowed (manage with /cbadmin reset add/remove/reset/set command) + # How many resets a player is allowed (manage with /chadmin reset add/remove/reset/set command) # Value of -1 means unlimited, 0 means hardcore - no resets. # Example, 2 resets means they get 2 resets or 3 islands lifetime reset-limit: -1 @@ -560,7 +560,7 @@ island: # # Here are some examples of valid commands to execute: # * "[SUDO] bbox version" - # * "cbadmin deaths set [player] 0" + # * "chadmin deaths set [player] 0" on-join: [] # List of commands to run when a player leaves an island, resets his island or gets kicked from it. # These commands are run by the console, unless otherwise stated using the [SUDO] prefix, @@ -571,7 +571,7 @@ island: # # Here are some examples of valid commands to execute: # * '[SUDO] bbox version' - # * 'cbadmin deaths set [player] 0' + # * 'chadmin deaths set [player] 0' # # Note that player-executed commands might not work, as these commands can be run with said player being offline. on-leave: [] @@ -584,7 +584,7 @@ island: # # Here are some examples of valid commands to execute: # * '[SUDO] bbox version' - # * 'cbadmin deaths set [player] 0' + # * 'chadmin deaths set [player] 0' # # Note that player-executed commands might not work, as these commands can be run with said player being offline. on-respawn: [] diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml index 739d786..dbf0f44 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -7,7 +7,7 @@ protection: &b kouzelný blok, pokud &b dokáže rozbíjet bloky. hint: "&c Vaše hodnost nemůže rozbít kouzelný blok!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Počáteční Bezpečnost description: |- &b Zabrání novým hráčům @@ -15,12 +15,12 @@ protection: &b aby nespadli. hint: "&c Pohyb zablokován kvůli bezpečnosti na [number] sekund!" free-to-move: "&a Můžete se volně pohybovat. Buďte opatrní!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Zobrazuje stavový panel &b pro každou fázi. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Zobrazuje stav diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index 163b1fc..5cd4cb5 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -7,7 +7,7 @@ protection: &b Block zerstören kann, falls &b Blöcke zerstört werden können. hint: "&c Dein Rang kann den magischen Block nicht zerstören!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Start-Sicherheit description: |- &b Verhindert, dass neue Spieler @@ -15,12 +15,12 @@ protection: &b damit sie nicht herunterfallen. hint: "&c Bewegung aus Sicherheitsgründen für [number] weitere Sekunden blockiert!" free-to-move: "&a Du kannst dich frei bewegen. Sei vorsichtig!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss-Balken description: |- &b Zeigt eine Statusleiste &b für jede Phase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action-Leiste description: |- &b Zeigt einen Status diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index b2c2016..aae8140 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -11,7 +11,7 @@ protection: &b Rank that can break the magic &b block if they can break blocks. hint: "&c Your rank cannot break the magic block!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Starting Safety description: |- &b Prevents new players @@ -19,12 +19,12 @@ protection: &b so they don't fall off. hint: "&c Movement blocked for safety for [number] more seconds!" free-to-move: "&a You are free to move. Be careful!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Shows a status bar &b for each phase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Shows a status @@ -132,7 +132,7 @@ chunkblock: count: "&a Island is on block &b [number] &a in the &b [name] &a phase. Lifetime count &b [lifetime] &a." phases: description: show a list of all the phases - title: "&2 OneBlock Phases" + title: "&2 ChunkBlock Phases" name-syntax: "&a [name]" description-syntax: "&b [number] blocks" island: @@ -164,7 +164,7 @@ chunkblock: my-island-phase-default: Unknown gui: titles: - phases: '&0&l OneBlock Phases' + phases: '&0&l ChunkBlock Phases' # This section contains all button names and lore (description) buttons: # List of buttons in GUI's diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index 551b99b..3558929 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -7,7 +7,7 @@ protection: &b bloque mágico si puede &b romper bloques. hint: "&c ¡Tu rango no puede romper el bloque mágico!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Seguridad Inicial description: |- &b Evita que los nuevos jugadores @@ -15,12 +15,12 @@ protection: &b para que no se caigan. hint: "&c Movimiento bloqueado por seguridad durante [number] segundos más!" free-to-move: "&a Eres libre de moverte. ¡Ten cuidado!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Barra de Jefe (Boss Bar) description: |- &b Muestra una barra de estado &b para cada fase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Barra de Acción (Action Bar) description: |- &b Muestra un estado diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index b59276c..531f1a4 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -7,7 +7,7 @@ protection: &b magique s'il peut casser &b des blocs. hint: "&c Votre rang ne peut pas casser le bloc magique!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Sécurité de Départ description: |- &b Empêche les nouveaux joueurs @@ -15,12 +15,12 @@ protection: &b pour qu'ils ne tombent pas. hint: "&c Mouvement bloqué par sécurité pendant [number] secondes supplémentaires!" free-to-move: "&a Vous êtes libre de bouger. Faites attention!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Barre de Boss description: |- &b Affiche une barre de statut &b pour chaque phase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Barre d'Action description: |- &b Affiche un statut diff --git a/src/main/resources/locales/hr.yml b/src/main/resources/locales/hr.yml index 21d3da7..6cb4138 100644 --- a/src/main/resources/locales/hr.yml +++ b/src/main/resources/locales/hr.yml @@ -7,7 +7,7 @@ protection: &b magični blok, ako &b može razbiti blokove. hint: "&c Vaš rang ne može razbiti magični blok!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Početna Sigurnost description: |- &b Sprječava nove igrače @@ -15,12 +15,12 @@ protection: &b da ne padnu. hint: "&c Kretanje je blokirano iz sigurnosnih razloga još [number] sekundi!" free-to-move: "&a Slobodni ste za kretanje. Budite oprezni!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Traka description: |- &b Prikazuje statusnu traku &b za svaku fazu. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Traka Akcije description: |- &b Prikazuje status diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index cc68103..aaba727 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -8,7 +8,7 @@ protection: &b blokkot, ha tud blokkokat &b rombolni. hint: "&c A rangod nem törheti szét a mágikus blokkot!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Kezdő Biztonság description: |- &b Megakadályozza az új játékosokat @@ -16,12 +16,12 @@ protection: &b hogy ne essenek le. hint: "&c Mozgás blokkolva biztonsági okokból még [number] másodpercig!" free-to-move: "&a Szabadon mozoghatsz. Légy óvatos!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Állapotjelző sávot &b mutat minden fázishoz. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Műveleti Sáv (Action Bar) description: |- &b Állapotot mutat diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index 23bf9e9..dce0c28 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -7,7 +7,7 @@ protection: &b menghancurkan blok ajaib &b jika mereka dapat menghancurkan blok. hint: "&c Pangkatmu tidak dapat menghancurkan blok ajaib!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Keamanan Awal description: |- &b Mencegah pemain baru @@ -15,12 +15,12 @@ protection: &b agar mereka tidak jatuh. hint: "&c Pergerakan diblokir untuk keselamatan selama [number] detik lagi!" free-to-move: "&a Anda bebas bergerak. Hati-hati!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Menampilkan bilah status &b untuk setiap fase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Menampilkan status @@ -100,7 +100,7 @@ chunkblock: my-island-phase-default: Tidak diketahui gui: titles: - phases: OneBlock Phases + phases: ChunkBlock Phases buttons: previous: name: '&f&l halaman sebelumnya' diff --git a/src/main/resources/locales/it.yml b/src/main/resources/locales/it.yml index 0c63978..f9bcbf4 100644 --- a/src/main/resources/locales/it.yml +++ b/src/main/resources/locales/it.yml @@ -7,7 +7,7 @@ protection: &b blocco magico se può &b rompere i blocchi. hint: "&c Il tuo rango non può rompere il blocco magico!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Sicurezza Iniziale description: |- &b Impedisce ai nuovi giocatori @@ -15,12 +15,12 @@ protection: &b in modo che non cadano. hint: "&c Movimento bloccato per sicurezza per [number] secondi ancora!" free-to-move: "&a Sei libero di muoverti. Fai attenzione!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Mostra una barra di stato &b per ogni fase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Mostra uno stato diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml index 328e6c1..fc08cba 100644 --- a/src/main/resources/locales/ja.yml +++ b/src/main/resources/locales/ja.yml @@ -7,7 +7,7 @@ protection: &b 魔法ブロックを破壊できる &b ランク。 hint: "&c あなたのランクでは魔法ブロックを破壊できません!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: 開始時の安全対策 description: |- &b 新しいプレイヤーが @@ -15,12 +15,12 @@ protection: &b 落下を防ぎます。 hint: "&c 安全のため、あと [number] 秒間移動がブロックされています!" free-to-move: "&a 自由に動けます。注意してください!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: ボスバー description: |- &b 各フェーズの &b ステータスバーを表示します。 - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: アクションバー description: |- &b 各フェーズの diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml index a955774..24e7225 100644 --- a/src/main/resources/locales/pl.yml +++ b/src/main/resources/locales/pl.yml @@ -7,7 +7,7 @@ protection: &b magiczny blok, jeśli &b może niszczyć bloki. hint: "&c Twoja ranga nie może zniszczyć magicznego bloku!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Bezpieczeństwo Początkowe description: |- &b Zapobiega poruszaniu się @@ -15,12 +15,12 @@ protection: &b aby nie spadli. hint: "&c Ruch zablokowany ze względów bezpieczeństwa na kolejne [number] sekund!" free-to-move: "&a Możesz się swobodnie poruszać. Bądź ostrożny!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Pasek Bossa description: |- &b Pokazuje pasek statusu &b dla każdej fazy. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Pasek Akcji description: |- &b Pokazuje status diff --git a/src/main/resources/locales/pt.yml b/src/main/resources/locales/pt.yml index 1289c9f..ef33ccb 100644 --- a/src/main/resources/locales/pt.yml +++ b/src/main/resources/locales/pt.yml @@ -7,7 +7,7 @@ protection: &b bloco mágico se puder &b quebrar blocos. hint: "&c Seu rank não pode quebrar o bloco mágico!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Segurança Inicial description: |- &b Impede que novos jogadores @@ -15,12 +15,12 @@ protection: &b para que não caiam. hint: "&c Movimento bloqueado por segurança por mais [number] segundos!" free-to-move: "&a Você está livre para se mover. Tenha cuidado!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Mostra uma barra de status &b para cada fase. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Mostra um status diff --git a/src/main/resources/locales/ru.yml b/src/main/resources/locales/ru.yml index b7485f4..1f3ece3 100644 --- a/src/main/resources/locales/ru.yml +++ b/src/main/resources/locales/ru.yml @@ -11,17 +11,17 @@ protection: Предотвращает ломание магического блока hint: Ломание магического блока запрещено! - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Защита новичков от падений description: |- Запрещает новичкам двигаться в течение 1 минуты hint: Движение заблокировано в целях безопасности ещё на [number] секунд! free-to-move: Теперь вы можете двигаться. Будьте осторожны! - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Боссбар description: Показывает статус фазы в боссбаре - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Панель действий description: Показывает статус фазы в панели действий chunkblock: diff --git a/src/main/resources/locales/tr.yml b/src/main/resources/locales/tr.yml index 5c351e6..351ac03 100644 --- a/src/main/resources/locales/tr.yml +++ b/src/main/resources/locales/tr.yml @@ -7,7 +7,7 @@ protection: &b büyülü bloğu kırabilecek &b rütbe. hint: "&c Rütbeniz büyülü bloğu kıramaz!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Başlangıç Güvenliği description: |- &b Yeni oyuncuların 1 dakika @@ -15,12 +15,12 @@ protection: &b engelleyerek düşmelerini önler. hint: "&c Güvenlik için hareket [number] saniye daha engellendi!" free-to-move: "&a Serbestçe hareket edebilirsiniz. Dikkatli olun!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Çubuğu description: |- &b Her aşama için bir &b durum çubuğu gösterir. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Eylem Çubuğu (Action Bar) description: |- &b Her aşama için bir diff --git a/src/main/resources/locales/uk.yml b/src/main/resources/locales/uk.yml index c093f22..76b12bb 100644 --- a/src/main/resources/locales/uk.yml +++ b/src/main/resources/locales/uk.yml @@ -7,7 +7,7 @@ protection: &b магічний блок, якщо &b може ламати блоки. hint: "&c Ваш ранг не може зламати магічний блок!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: Початкова Безпека description: |- &b Запобігає руху нових гравців @@ -15,12 +15,12 @@ protection: &b щоб вони не впали. hint: "&c Рух заблоковано з міркувань безпеки ще на [number] секунд!" free-to-move: "&a Ви можете вільно рухатися. Будьте обережні!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss Bar description: |- &b Показує панель стану &b для кожної фази. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Action Bar description: |- &b Показує статус diff --git a/src/main/resources/locales/vi.yml b/src/main/resources/locales/vi.yml index 1d04675..53510a5 100644 --- a/src/main/resources/locales/vi.yml +++ b/src/main/resources/locales/vi.yml @@ -7,7 +7,7 @@ protection: &b khối ma thuật nếu họ &b có thể phá khối. hint: "&c Xếp hạng của bạn không thể phá khối ma thuật!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: An Toàn Khởi Đầu description: |- &b Ngăn người chơi mới @@ -15,12 +15,12 @@ protection: &b để họ không bị rơi. hint: "&c Di chuyển bị chặn vì lý do an toàn trong [number] giây nữa!" free-to-move: "&a Bạn được phép di chuyển tự do. Hãy cẩn thận!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Thanh Boss description: |- &b Hiển thị thanh trạng thái &b cho mỗi giai đoạn. - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: Thanh Hành Động (Action Bar) description: |- &b Hiển thị trạng thái diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index d096b91..dc17eb0 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -7,19 +7,19 @@ protection: &b 则该等级可以破坏 &b 魔法方块。 hint: "&c 您的等级无法破坏魔法方块!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: 初始安全保护 description: |- &b 阻止新玩家在1分钟内 &b 移动,以防他们跌落。 hint: "&c 出于安全考虑,移动已被阻止 [number] 秒!" free-to-move: "&a 您可以自由移动了。请小心!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss 血条 description: |- &b 为每个阶段 &b 显示一个状态条。 - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: 动作栏 description: |- &b 在动作栏中 diff --git a/src/main/resources/locales/zh-TW.yml b/src/main/resources/locales/zh-TW.yml index 46c44b5..b3dd13f 100644 --- a/src/main/resources/locales/zh-TW.yml +++ b/src/main/resources/locales/zh-TW.yml @@ -7,19 +7,19 @@ protection: &b 則該等級可以破壞 &b 魔法方塊。 hint: "&c 您的等級無法破壞魔法方塊!" - START_SAFETY: + CHUNKBLOCK_START_SAFETY: name: 初始安全保護 description: |- &b 阻止新玩家在1分鐘內 &b 移動,以防他們跌落。 hint: "&c 出於安全考慮,移動已被阻止 [number] 秒!" free-to-move: "&a 您可以自由移動了。請小心!" - ONEBLOCK_BOSSBAR: + CHUNKBLOCK_BOSSBAR: name: Boss 血條 description: |- &b 為每個階段 &b 顯示一個狀態條。 - ONEBLOCK_ACTIONBAR: + CHUNKBLOCK_ACTIONBAR: name: 動作欄 description: |- &b 在動作欄中 diff --git a/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java b/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java index 71d4fa7..8c6d1e5 100644 --- a/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java +++ b/src/test/java/world/bentobox/chunkblock/ChunkBlockTest.java @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.jar.JarEntry; @@ -34,6 +35,7 @@ import world.bentobox.chunkblock.dataobjects.OneBlockIslands; import world.bentobox.bentobox.Settings; +import world.bentobox.bentobox.api.addons.Addon; import world.bentobox.bentobox.api.addons.Addon.State; import world.bentobox.bentobox.api.addons.AddonDescription; import world.bentobox.bentobox.api.user.User; @@ -138,9 +140,10 @@ public void setUp() throws Exception { AddonDescription desc = new AddonDescription.Builder("bentobox", "chunkblock", "1.3").description("test") .authors("tasty").build(); addon.setDescription(desc); - // Addons manager + // Addons manager - the Level addon is present by default AddonsManager am = mock(AddonsManager.class); when(plugin.getAddonsManager()).thenReturn(am); + when(am.getAddonByName("Level")).thenReturn(Optional.of(mock(Addon.class))); // Flags manager when(plugin.getFlagsManager()).thenReturn(fm); @@ -175,6 +178,23 @@ void testOnEnable() { } + /** + * Test that ChunkBlock disables itself, and takes its flags with it, when the + * Level addon is not on the server. + */ + @Test + void testOnEnableNoLevelAddonDisables() { + testOnLoad(); + addon.setState(State.ENABLED); + when(plugin.getAddonsManager().getAddonByName("Level")).thenReturn(Optional.empty()); + addon.onEnable(); + assertEquals(State.DISABLED, addon.getState()); + // The flags registered in onLoad must be unregistered so no listeners linger + verify(fm).unregister(addon); + // Nothing else was initialized + assertNull(addon.getBlockListener()); + } + /** * Test method for {@link world.bentobox.chunkblock.ChunkBlock#onLoad()}. */ diff --git a/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java b/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java index d69c7c1..62d33b4 100644 --- a/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java +++ b/src/test/java/world/bentobox/chunkblock/CommonTestSetup.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; @@ -202,6 +203,7 @@ public void setUp() throws Exception { when(island.isAllowed(any(User.class), any())).thenReturn(false); when(island.getOwner()).thenReturn(uuid); when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid)); + when(island.getMemberSet(anyInt())).thenReturn(ImmutableSet.of(uuid)); // Enable reporting from Flags class @SuppressWarnings("deprecation") diff --git a/src/test/java/world/bentobox/chunkblock/SettingsTest.java b/src/test/java/world/bentobox/chunkblock/SettingsTest.java index 440af2c..bc2a554 100644 --- a/src/test/java/world/bentobox/chunkblock/SettingsTest.java +++ b/src/test/java/world/bentobox/chunkblock/SettingsTest.java @@ -1059,7 +1059,7 @@ void testSetBanLimit() { */ @Test void testGetPlayerCommandAliases() { - assertEquals("cb chunkblock",s.getPlayerCommandAliases()); + assertEquals("ch chunkblock",s.getPlayerCommandAliases()); } /** @@ -1067,7 +1067,7 @@ void testGetPlayerCommandAliases() { */ @Test void testSetPlayerCommandAliases() { - assertEquals("cb chunkblock",s.getPlayerCommandAliases()); + assertEquals("ch chunkblock",s.getPlayerCommandAliases()); s.setPlayerCommandAliases("aliases"); assertEquals("aliases",s.getPlayerCommandAliases()); } @@ -1077,7 +1077,7 @@ void testSetPlayerCommandAliases() { */ @Test void testGetAdminCommandAliases() { - assertEquals("cbadmin chunkblockadmin cba",s.getAdminCommandAliases()); + assertEquals("chadmin chunkblockadmin cha",s.getAdminCommandAliases()); } /** @@ -1085,7 +1085,7 @@ void testGetAdminCommandAliases() { */ @Test void testSetAdminCommandAliases() { - assertEquals("cbadmin chunkblockadmin cba",s.getAdminCommandAliases()); + assertEquals("chadmin chunkblockadmin cha",s.getAdminCommandAliases()); s.setAdminCommandAliases("aliases"); assertEquals("aliases",s.getAdminCommandAliases()); } diff --git a/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java index 1b0ed66..1ac17ba 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/BossBarListenerTest.java @@ -4,6 +4,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -15,12 +16,16 @@ import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.boss.BossBar; +import org.bukkit.event.player.PlayerJoinEvent; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import net.kyori.adventure.text.Component; +import world.bentobox.bentobox.api.events.flags.FlagSettingChangeEvent; +import world.bentobox.bentobox.api.events.island.IslandEnterEvent; +import world.bentobox.bentobox.api.events.island.IslandExitEvent; import world.bentobox.chunkblock.ChunkBlock; import world.bentobox.chunkblock.CommonTestSetup; import world.bentobox.chunkblock.Settings; @@ -60,6 +65,8 @@ public void setUp() throws Exception { doNothing().when(addon).logError(anyString()); doReturn(obm).when(addon).getOneBlockManager(); doReturn(obi).when(addon).getOneBlocksIsland(any()); + // The addon enabled normally and has its world + doReturn(world).when(addon).getOverWorld(); // Phase progress when(obi.getPhaseName()).thenReturn("Plains"); @@ -92,7 +99,7 @@ private void fireMagicBlockEvent() { */ @Test void testActionBarShownWhenEnabled() { - when(island.isAllowed(addon.ONEBLOCK_ACTIONBAR)).thenReturn(true); + when(island.isAllowed(addon.CHUNKBLOCK_ACTIONBAR)).thenReturn(true); fireMagicBlockEvent(); verify(mockPlayer).sendActionBar(any(Component.class)); } @@ -105,7 +112,7 @@ void testActionBarShownWhenEnabled() { @Test void testActionBarNotShownWhenDisabledInConfig() { settings.setActionBar(false); - when(island.isAllowed(addon.ONEBLOCK_ACTIONBAR)).thenReturn(true); + when(island.isAllowed(addon.CHUNKBLOCK_ACTIONBAR)).thenReturn(true); fireMagicBlockEvent(); verify(mockPlayer, never()).sendActionBar(any(Component.class)); } @@ -125,7 +132,7 @@ void testActionBarNotShownWhenFlagDenied() { */ @Test void testBossBarShownWhenEnabled() { - when(island.isAllowed(addon.ONEBLOCK_BOSSBAR)).thenReturn(true); + when(island.isAllowed(addon.CHUNKBLOCK_BOSSBAR)).thenReturn(true); fireMagicBlockEvent(); verify(bossBar).addPlayer(mockPlayer); } @@ -136,9 +143,40 @@ void testBossBarShownWhenEnabled() { @Test void testBossBarNotShownWhenDisabledInConfig() { settings.setBossBar(false); - when(island.isAllowed(addon.ONEBLOCK_BOSSBAR)).thenReturn(true); + when(island.isAllowed(addon.CHUNKBLOCK_BOSSBAR)).thenReturn(true); fireMagicBlockEvent(); mockedBukkit.verify(() -> Bukkit.createBossBar(anyString(), any(), any()), never()); verify(bossBar, never()).addPlayer(any()); } + + /** + * Every handler must be inert when the addon never enabled and has no worlds — the + * listener is registered with the flags in onLoad, so it outlives a load failure + * (e.g. Level missing) and global events keep reaching it. No handler may throw. + */ + @Test + void testAllHandlersInertWhenAddonNeverEnabled() { + doReturn(null).when(addon).getOverWorld(); + when(island.isAllowed(addon.CHUNKBLOCK_BOSSBAR)).thenReturn(true); + + fireMagicBlockEvent(); + + IslandEnterEvent enter = mock(IslandEnterEvent.class); + bbl.onEnterIsland(enter); + + IslandExitEvent exit = mock(IslandExitEvent.class); + bbl.onExitIsland(exit); + + FlagSettingChangeEvent flagChange = mock(FlagSettingChangeEvent.class); + bbl.onFlagChange(flagChange); + + PlayerJoinEvent join = mock(PlayerJoinEvent.class); + when(join.getPlayer()).thenReturn(mockPlayer); + bbl.onJoin(join); + + // Nothing happened + mockedBukkit.verify(() -> Bukkit.createBossBar(anyString(), any(), any()), never()); + verify(bossBar, never()).addPlayer(any()); + verify(bossBar, never()).removePlayer(any()); + } } diff --git a/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java index c3f00a6..2e8d19b 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/ChunkClaimListenerTest.java @@ -6,6 +6,7 @@ import static org.mockito.ArgumentMatchers.anyInt; 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; @@ -14,6 +15,7 @@ import org.bukkit.GameMode; import org.bukkit.Location; +import org.bukkit.block.Block; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.EquipmentSlot; @@ -142,4 +144,49 @@ void testClaimingChainsOutward() { listener.onBorderHit(hit(Action.LEFT_CLICK_AIR)); assertTrue(data.isChunkUnlocked(2, 0)); } + + private PlayerInteractEvent hitBlock(Action action, int x, int z) { + Block block = mock(Block.class); + when(block.getX()).thenReturn(x); + when(block.getZ()).thenReturn(z); + return new PlayerInteractEvent(mockPlayer, action, null, block, org.bukkit.block.BlockFace.EAST, + EquipmentSlot.HAND); + } + + @Test + void testMiningOwnBlockNearBorderIsNotAClaim() { + // The generator-mining bug: clicked block is in the player's own chunk, but the + // aim line would cross into the locked neighbor. Must not probe a claim. + level = 0; + listener.onBorderHit(hitBlock(Action.LEFT_CLICK_BLOCK, 14, 8)); + assertFalse(data.isChunkUnlocked(1, 0)); + verify(notifier, never()).notify(any(), any()); + } + + @Test + void testPunchingBlockInLockedChunkClaimsIt() { + level = 1; + listener.onBorderHit(hitBlock(Action.LEFT_CLICK_BLOCK, 17, 8)); + assertTrue(data.isChunkUnlocked(1, 0)); + } + + @Test + void testNoCreditFeedbackIsThrottled() { + level = 0; + listener.onBorderHit(hit(Action.LEFT_CLICK_AIR)); + listener.onBorderHit(hit(Action.LEFT_CLICK_AIR)); + // Two rapid failed probes, one nag + verify(notifier, times(1)).notify(any(), any()); + } + + @Test + void testAimLineBlockedBySolidWallDoesNotClaim() { + level = 1; + // A solid wall of the player's own blocks between eye and border + Block wall = mock(Block.class); + when(wall.isPassable()).thenReturn(false); + when(world.getBlockAt(any(Location.class))).thenReturn(wall); + listener.onBorderHit(hit(Action.LEFT_CLICK_AIR)); + assertFalse(data.isChunkUnlocked(1, 0)); + } } diff --git a/src/test/java/world/bentobox/chunkblock/listeners/ChunkGuardListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/ChunkGuardListenerTest.java index 119a454..7f0d3bf 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/ChunkGuardListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/ChunkGuardListenerTest.java @@ -23,6 +23,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.google.common.collect.ImmutableSet; + +import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Util; import world.bentobox.chunkblock.ChunkBlock; import world.bentobox.chunkblock.CommonTestSetup; @@ -184,4 +187,25 @@ void testLockIsTwoDimensional() { listener.onPlayerMove(e); assertTrue(e.isCancelled()); } + + @Test + void testBacktrackNonMemberSentHomeNotEjectedLocally() { + // Player is inside a locked chunk of an island they have no rank on (e.g. a + // respawn that landed on a stranger's or abandoned island) + when(mockPlayer.getLocation()).thenReturn(lockedTo); + when(island.getMemberSet(RanksManager.COOP_RANK)).thenReturn(ImmutableSet.of()); + listener.backtrack(mockPlayer); + verify(im).homeTeleportAsync(world, mockPlayer); + mockedUtil.verify(() -> Util.teleportAsync(any(Player.class), any(Location.class)), never()); + } + + @Test + void testBacktrackMemberEjectedWithinIsland() { + when(mockPlayer.getLocation()).thenReturn(lockedTo); + when(island.getMemberSet(RanksManager.COOP_RANK)).thenReturn(ImmutableSet.of(uuid)); + when(im.isSafeLocation(any())).thenReturn(true); + listener.backtrack(mockPlayer); + verify(im, never()).homeTeleportAsync(any(), any(Player.class)); + mockedUtil.verify(() -> Util.teleportAsync(any(Player.class), any(Location.class))); + } } diff --git a/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java b/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java index fb442db..432d976 100644 --- a/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java +++ b/src/test/java/world/bentobox/chunkblock/listeners/StartSafetyListenerTest.java @@ -75,7 +75,7 @@ public void setUp() throws Exception { when(location2.getX()).thenReturn(0.5D); - addon.START_SAFETY.setSetting(world, true); + addon.CHUNKBLOCK_START_SAFETY.setSetting(world, true); ssl = new StartSafetyListener(addon); } diff --git a/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java b/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java index f1b33d2..c417882 100644 --- a/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java +++ b/src/test/java/world/bentobox/chunkblock/panels/PhasesPanelTest.java @@ -42,6 +42,7 @@ import world.bentobox.bank.Bank; import world.bentobox.bank.BankManager; import world.bentobox.bank.data.Money; +import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.panels.TemplatedPanel; import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; import world.bentobox.bentobox.api.user.User; @@ -554,6 +555,84 @@ void testCanApplyPhaseSuccess() throws Exception { assertTrue(result); } + /** + * Test canApplyPhase returns false when the player lacks the setcount command + * permission — the "click to change" action must not be offered. + */ + @Test + void testCanApplyPhaseNoSetCountPermission() throws Exception { + setUpAddonMocks(); + Island testIsland = new Island(); + testIsland.setOwner(uuid); + OneBlockIslands oneBlockIsland = new OneBlockIslands(testIsland.getUniqueId()); + oneBlockIsland.setLifetime(100L); + oneBlockIsland.setLastPhaseChangeTime(System.currentTimeMillis()); + + User user = User.getInstance(mockPlayer); + when(im.getIsland(world, user)).thenReturn(testIsland); + when(blockListener.getIsland(testIsland)).thenReturn(oneBlockIsland); + when(oneBlockManager.getBlockProbs()).thenReturn(createBlockProbs()); + + // The setcount command exists and carries a permission the player does not have + CompositeCommand mainCommand = mock(CompositeCommand.class); + CompositeCommand setCountCommand = mock(CompositeCommand.class); + when(mainCommand.getSubCommand("setcount")).thenReturn(Optional.of(setCountCommand)); + when(setCountCommand.getPermission()).thenReturn("chunkblock.island.setcount"); + when(addon.getPlayerCommand()).thenReturn(Optional.of(mainCommand)); + when(mockPlayer.hasPermission("chunkblock.island.setcount")).thenReturn(false); + + Constructor constructor = PhasesPanel.class.getDeclaredConstructor(ChunkBlock.class, World.class, User.class); + constructor.setAccessible(true); + panel = constructor.newInstance(addon, world, user); + + OneBlockPhase phase = createTestPhase("Plains"); + + Method method = PhasesPanel.class.getDeclaredMethod("canApplyPhase", OneBlockPhase.class); + method.setAccessible(true); + + boolean result = (boolean) method.invoke(panel, phase); + + assertFalse(result); + } + + /** + * Test canApplyPhase returns true when the player holds the setcount permission. + */ + @Test + void testCanApplyPhaseWithSetCountPermission() throws Exception { + setUpAddonMocks(); + Island testIsland = new Island(); + testIsland.setOwner(uuid); + OneBlockIslands oneBlockIsland = new OneBlockIslands(testIsland.getUniqueId()); + oneBlockIsland.setLifetime(100L); + oneBlockIsland.setLastPhaseChangeTime(System.currentTimeMillis()); + + User user = User.getInstance(mockPlayer); + when(im.getIsland(world, user)).thenReturn(testIsland); + when(blockListener.getIsland(testIsland)).thenReturn(oneBlockIsland); + when(oneBlockManager.getBlockProbs()).thenReturn(createBlockProbs()); + + CompositeCommand mainCommand = mock(CompositeCommand.class); + CompositeCommand setCountCommand = mock(CompositeCommand.class); + when(mainCommand.getSubCommand("setcount")).thenReturn(Optional.of(setCountCommand)); + when(setCountCommand.getPermission()).thenReturn("chunkblock.island.setcount"); + when(addon.getPlayerCommand()).thenReturn(Optional.of(mainCommand)); + when(mockPlayer.hasPermission("chunkblock.island.setcount")).thenReturn(true); + + Constructor constructor = PhasesPanel.class.getDeclaredConstructor(ChunkBlock.class, World.class, User.class); + constructor.setAccessible(true); + panel = constructor.newInstance(addon, world, user); + + OneBlockPhase phase = createTestPhase("Plains"); + + Method method = PhasesPanel.class.getDeclaredMethod("canApplyPhase", OneBlockPhase.class); + method.setAccessible(true); + + boolean result = (boolean) method.invoke(panel, phase); + + assertTrue(result); + } + // ========================================================================= // Test phaseRequirementsFail // =========================================================================