From 376ac167e827aed3067369f8925196414504d8b5 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 10 Jul 2021 18:27:57 -0700 Subject: [PATCH] Version 1.17.1 (#1796) * Version 1.17.1 * Add homes command to default player options. * Implements better online player counter. (#1791) Current one is based on online player count when bStats sends data to the server. This one will send data about amount of players who logged in the server. * Add Boxed Gamemode to the list. (#1793) * Add Bank addon (#1792) * Add Holographic Displays as SoftDepend for AOneBlock (#1794) * Custom date time format support for / info (#1783) Fixes #1720 * Parent/sub-flag support, split up and designate CONTAINER flag as parent flag (#1784) * Split CONTAINER flag into multiple flags CONTAINER split into - CONTAINER (Chest/Minecart Chest) - BARREL (Barrel) - COMPOSTER (Composter) - FLOWER_POT (Flower Pot) - SHULKER_BOX (Shulker Box) - TRAPPED_CHEST (Trapped Chest) Fixes #1777 * Add subflag support * Create container parent flag, chest subflag * Remove extra string from when CHEST was CONTAINER * Fix incorrect flag specified on fired event in IslandToggleClick * Add missing world subflag event firing * Remove extra import * Add world setting flag for island visitors keep inventory (#1785) * Implement 3 bar charts: addons, gamemodes, hooks (#1790) BStats supports sending Bar chart data, however, it does not display it via their site directly. It can be called manually, to view. PieChart does not work very well for addons and hooks. BarChart however allows viewing each addon separately. This change allows sending data to the server about bar charts. * Update action versions * Declare distribution - adopt * Cache Java Maven files differently. * Try dependency for shade snapshot * Add support for Minecraft 1.17.1 * Fix test by incrementing listener value check There is a new listener now. * Spigot 1.17.1 * Fix test for InventoryListener * Re-order repos. * Downgrading to 1.17 Spigot for now to enable building. * Try pluginRespositories tag in POM to enable Github actions Co-authored-by: BONNe Co-authored-by: Fredthedoggy <45927799+Fredthedoggy@users.noreply.github.com> Co-authored-by: Justin --- .github/workflows/build.yml | 18 ++--- ADDON.md | 1 + README.md | 1 + pom.xml | 49 +++++-------- .../java/world/bentobox/bentobox/BStats.java | 69 ++++++++++++++++++- .../commands/island/DefaultPlayerCommand.java | 1 + .../bentobox/bentobox/api/flags/Flag.java | 50 ++++++++++++++ .../api/flags/clicklisteners/CycleClick.java | 12 ++++ .../clicklisteners/IslandToggleClick.java | 5 ++ .../clicklisteners/WorldToggleClick.java | 6 ++ .../bentobox/database/objects/Island.java | 60 ++++++++++++++-- .../bentobox/listeners/JoinLeaveListener.java | 3 + .../protection/BlockInteractionListener.java | 14 +++- .../flags/protection/InventoryListener.java | 28 ++++++++ .../VisitorKeepInventoryListener.java | 39 +++++++++++ .../world/bentobox/bentobox/lists/Flags.java | 20 +++++- .../versions/ServerCompatibility.java | 6 +- src/main/resources/locales/en-US.yml | 52 ++++++++++++-- src/main/resources/plugin.yml | 1 + .../BlockInteractionListenerTest.java | 14 ++-- .../bentobox/managers/FlagsManagerTest.java | 2 +- 21 files changed, 386 insertions(+), 65 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/VisitorKeepInventoryListener.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 261665e62..f2e3dc802 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,21 +14,23 @@ jobs: with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Set up JDK 16 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: - java-version: 16 + distribution: 'adopt' + java-version: '16' - name: Cache SonarCloud packages - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - - name: Cache Maven packages - uses: actions/cache@v1 + - name: Cache local Maven repository + uses: actions/cache@v2 with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- - name: Build run: mvn --batch-mode clean org.jacoco:jacoco-maven-plugin:prepare-agent install - run: mkdir staging && cp target/*.jar staging diff --git a/ADDON.md b/ADDON.md index ae5386c67..01f9fc49c 100644 --- a/ADDON.md +++ b/ADDON.md @@ -1,4 +1,5 @@ The following is a list of all addons currently made for BentoBox: +* [**Bank**](https://github.com/BentoBoxWorld/Bank/): Provides an island bank to enable island members to share money. * [**Biomes**](https://github.com/BentoBoxWorld/Biomes/): Enables players to change biomes on islands. * [**Border**](https://github.com/BentoBoxWorld/Border/): Adds a world border around islands. * [**Cauldron Witchery**](https://github.com/BentoBoxWorld/CauldronWitchery/): Allows summoning mobs using some magic! diff --git a/README.md b/README.md index 1806f8064..ea309b227 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Start now to create the server you've dreamed of! These are some popular Gamemodes: * [**AcidIsland**](https://github.com/BentoBoxWorld/AcidIsland): You are marooned in a sea of acid! * [**AOneBlock**](https://github.com/BentoBoxWorld/AOneBlock): Start to play with only 1 magical block. +* [**Boxed**](https://github.com/BentoBoxWorld/Boxed): A game mode where you are boxed into a tiny space that only expands by completing advancements. * [**BSkyBlock**](https://github.com/BentoBoxWorld/BSkyBlock): The successor to the popular ASkyBlock. * [**CaveBlock**](https://github.com/BentoBoxWorld/CaveBlock): Try to live underground! * [**SkyGrid**](https://github.com/BentoBoxWorld/SkyGrid): Survive in world made up of scattered blocks - what an adventure! diff --git a/pom.xml b/pom.xml index 6d99349c3..c63f3cf3c 100644 --- a/pom.xml +++ b/pom.xml @@ -83,7 +83,7 @@ -LOCAL - 1.17.0 + 1.17.1 @@ -126,40 +126,16 @@ - - sonar - - https://sonarcloud.io - bentobox-world - - - - - org.sonarsource.scanner.maven - sonar-maven-plugin - - 3.6.0.1398 - - - verify - - sonar - - - - - - - + + + apache.snapshots + https://repository.apache.org/snapshots/ + + + - - - maven-snapshots - https://repository.apache.org/content/repositories/snapshots/ - spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots @@ -188,6 +164,13 @@ papermc https://papermc.io/repo/repository/maven-public/ + + + maven-snapshots + https://repository.apache.org/content/repositories/snapshots/ + + @@ -209,7 +192,7 @@ org.spigotmc spigot - 1.17-R0.1-SNAPSHOT + ${spigot.version} provided diff --git a/src/main/java/world/bentobox/bentobox/BStats.java b/src/main/java/world/bentobox/bentobox/BStats.java index dee4a6a86..aedf7c83a 100644 --- a/src/main/java/world/bentobox/bentobox/BStats.java +++ b/src/main/java/world/bentobox/bentobox/BStats.java @@ -1,10 +1,14 @@ package world.bentobox.bentobox; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import java.util.UUID; import org.bstats.bukkit.Metrics; import org.bstats.charts.AdvancedPie; +import org.bstats.charts.SimpleBarChart; import org.bstats.charts.SimplePie; import org.bstats.charts.SingleLineChart; import org.bukkit.Bukkit; @@ -29,6 +33,13 @@ public class BStats { */ private int islandsCreatedCount = 0; + /** + * Contains the amount of connected players since last data send. + * @since 1.17.1 + */ + private final Set connectedPlayerSet = new HashSet<>(); + + BStats(BentoBox plugin) { this.plugin = plugin; } @@ -53,6 +64,11 @@ private void registerCustomMetrics() { // Single Line charts registerIslandsCountChart(); registerIslandsCreatedChart(); + + // Bar Charts + registerAddonsBarChart(); + registerGameModeAddonsBarChart(); + registerHooksBarChart(); } private void registerDefaultLanguageChart() { @@ -86,6 +102,15 @@ public void increaseIslandsCreatedCount() { islandsCreatedCount++; } + /** + * Adds given UUID to the connected player set. + * @param uuid UUID of a player who logins. + * @since 1.17.1 + */ + public void addPlayer(UUID uuid) { + this.connectedPlayerSet.add(uuid); + } + /** * Sends the enabled addons (except GameModeAddons) of this server. * @since 1.1 @@ -132,7 +157,9 @@ private void registerHooksChart() { */ private void registerPlayersPerServerChart() { metrics.addCustomChart(new SimplePie("playersPerServer", () -> { - int players = Bukkit.getOnlinePlayers().size(); + int players = this.connectedPlayerSet.size(); + this.connectedPlayerSet.clear(); + if (players <= 0) return "0"; else if (players <= 10) return "1-10"; else if (players <= 30) return "11-30"; @@ -164,4 +191,44 @@ private void registerFlagsDisplayModeChart() { return values; })); } + + /** + * Sends the enabled addons (except GameModeAddons) of this server as bar chart. + * @since 1.17.1 + */ + private void registerAddonsBarChart() { + metrics.addCustomChart(new SimpleBarChart("addonsBar", () -> { + Map values = new HashMap<>(); + plugin.getAddonsManager().getEnabledAddons().stream() + .filter(addon -> !(addon instanceof GameModeAddon) && addon.getDescription().isMetrics()) + .forEach(addon -> values.put(addon.getDescription().getName(), 1)); + return values; + })); + } + + /** + * Sends the enabled GameModeAddons of this server as a bar chart. + * @since 1.17.1 + */ + private void registerGameModeAddonsBarChart() { + metrics.addCustomChart(new SimpleBarChart("gameModeAddonsBar", () -> { + Map values = new HashMap<>(); + plugin.getAddonsManager().getGameModeAddons().stream() + .filter(gameModeAddon -> gameModeAddon.getDescription().isMetrics()) + .forEach(gameModeAddon -> values.put(gameModeAddon.getDescription().getName(), 1)); + return values; + })); + } + + /** + * Sends the enabled Hooks of this server as a bar chart. + * @since 1.17.1 + */ + private void registerHooksBarChart() { + metrics.addCustomChart(new SimpleBarChart("hooksBar", () -> { + Map values = new HashMap<>(); + plugin.getHooks().getHooks().forEach(hook -> values.put(hook.getPluginName(), 1)); + return values; + })); + } } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/DefaultPlayerCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/DefaultPlayerCommand.java index 9a96cc4b2..64cf3f632 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/DefaultPlayerCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/DefaultPlayerCommand.java @@ -78,6 +78,7 @@ public void setup() { new IslandSethomeCommand(this); new IslandDeletehomeCommand(this); new IslandRenamehomeCommand(this); + new IslandHomesCommand(this); } diff --git a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java index c56474630..754cdf325 100644 --- a/src/main/java/world/bentobox/bentobox/api/flags/Flag.java +++ b/src/main/java/world/bentobox/bentobox/api/flags/Flag.java @@ -1,5 +1,6 @@ package world.bentobox.bentobox.api.flags; +import java.util.Arrays; import java.util.HashSet; import java.util.Optional; import java.util.Set; @@ -131,6 +132,7 @@ public boolean isGreaterThan(Mode rank) { private final Addon addon; private final int cooldown; private final Mode mode; + private final Set subflags; private Flag(Builder builder) { this.id = builder.id; @@ -147,6 +149,7 @@ private Flag(Builder builder) { this.cooldown = builder.cooldown; this.addon = builder.addon; this.mode = builder.mode; + this.subflags = builder.subflags; } public String getID() { @@ -200,6 +203,18 @@ public void setSetting(World world, boolean setting) { .getWorldSettings(world) .getWorldFlags() .put(getID(), setting); + + // Subflag support + if (hasSubflags()) { + subflags.stream() + .filter(subflag -> subflag.getType().equals(Type.WORLD_SETTING) || subflag.getType().equals(Type.PROTECTION)) + .forEach(subflag -> BentoBox.getInstance() + .getIWM() + .getWorldSettings(world) + .getWorldFlags() + .put(subflag.getID(), setting)); + } + // Save config file BentoBox.getInstance().getIWM().getAddon(world).ifPresent(GameModeAddon::saveWorldSettings); } @@ -208,6 +223,7 @@ public void setSetting(World world, boolean setting) { /** * Set the original status of this flag for locations outside of island spaces. * May be overriden by the the setting for this world. + * Does not affect subflags. * @param defaultSetting - true means it is allowed. false means it is not allowed */ public void setDefaultSetting(boolean defaultSetting) { @@ -217,6 +233,7 @@ public void setDefaultSetting(boolean defaultSetting) { /** * Set the status of this flag for locations outside of island spaces for a specific world. * World must exist and be registered before this method can be called. + * Does not affect subflags. * @param defaultSetting - true means it is allowed. false means it is not allowed */ public void setDefaultSetting(World world, boolean defaultSetting) { @@ -435,6 +452,22 @@ public Mode getMode() { return mode; } + /** + * @return whether the flag has subflags (and therefore is a parent flag) + * @since 1.17.0 + */ + public boolean hasSubflags() { + return !subflags.isEmpty(); + } + + /** + * @return the subflags, an empty Set if none + * @since 1.17.0 + */ + public Set getSubflags() { + return subflags; + } + @Override public String toString() { return "Flag [id=" + id + "]"; @@ -480,6 +513,9 @@ public static class Builder { // Mode private Mode mode = Mode.EXPERT; + // Subflags + private Set subflags; + /** * Builder for making flags * @param id - a unique id that MUST be the same as the enum of the flag @@ -488,6 +524,7 @@ public static class Builder { public Builder(String id, Material icon) { this.id = id; this.icon = icon; + this.subflags = new HashSet<>(); } /** @@ -595,6 +632,19 @@ public Builder mode(Mode mode) { return this; } + /** + * Add subflags and designate this flag as a parent flag. + * Subflags have their state simultaneously changed with the parent flag. + * Take extra care to ensure that subflags have the same number of possible values as the parent flag. + * @param flags all Flags that are subflags + * @return Builder - flag builder + * @since 1.17.0 + */ + public Builder subflags(Flag... flags) { + this.subflags.addAll(Arrays.asList(flags)); + return this; + } + /** * Build the flag * @return Flag diff --git a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java index 401e760de..922337133 100644 --- a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java +++ b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/CycleClick.java @@ -90,6 +90,12 @@ public boolean onClick(Panel panel, User user, ClickType click, int slot) { user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_OFF, 1F, 1F); // Fire event Bukkit.getPluginManager().callEvent(new FlagProtectionChangeEvent(island, user.getUniqueId(), flag, island.getFlag(flag))); + + // Subflag support + if (flag.hasSubflags()) { + // Fire events for all subflags as well + flag.getSubflags().forEach(subflag -> Bukkit.getPluginManager().callEvent(new FlagProtectionChangeEvent(island, user.getUniqueId(), subflag, island.getFlag(subflag)))); + } } else if (click.equals(ClickType.RIGHT)) { if (currentRank <= minRank) { island.setFlag(flag, maxRank); @@ -99,6 +105,12 @@ public boolean onClick(Panel panel, User user, ClickType click, int slot) { user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); // Fire event Bukkit.getPluginManager().callEvent(new FlagProtectionChangeEvent(island, user.getUniqueId(), flag, island.getFlag(flag))); + + // Subflag support + if (flag.hasSubflags()) { + // Fire events for all subflags as well + flag.getSubflags().forEach(subflag -> Bukkit.getPluginManager().callEvent(new FlagProtectionChangeEvent(island, user.getUniqueId(), subflag, island.getFlag(subflag)))); + } } else if (click.equals(ClickType.SHIFT_LEFT) && user.isOp()) { if (!plugin.getIWM().getHiddenFlags(user.getWorld()).contains(flag.getID())) { plugin.getIWM().getHiddenFlags(user.getWorld()).add(flag.getID()); diff --git a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java index 08dbba227..2afcd6b96 100644 --- a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java +++ b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/IslandToggleClick.java @@ -77,6 +77,11 @@ public boolean onClick(Panel panel, User user, ClickType click, int slot) { island.setCooldown(flag); // Fire event Bukkit.getPluginManager().callEvent(new FlagSettingChangeEvent(island, user.getUniqueId(), flag, island.isAllowed(flag))); + + if (flag.hasSubflags()) { + // Fire events for all subflags as well + flag.getSubflags().forEach(subflag -> Bukkit.getPluginManager().callEvent(new FlagSettingChangeEvent(island, user.getUniqueId(), subflag, island.isAllowed(subflag)))); + } } }); } else { diff --git a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/WorldToggleClick.java b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/WorldToggleClick.java index e33bbf623..cb8c0a0e1 100644 --- a/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/WorldToggleClick.java +++ b/src/main/java/world/bentobox/bentobox/api/flags/clicklisteners/WorldToggleClick.java @@ -61,6 +61,12 @@ public boolean onClick(Panel panel, User user, ClickType click, int slot) { user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_STONE_BUTTON_CLICK_ON, 1F, 1F); // Fire event Bukkit.getPluginManager().callEvent(new FlagWorldSettingChangeEvent(user.getWorld(), user.getUniqueId(), flag, flag.isSetForWorld(user.getWorld()))); + + // Subflag support + if (flag.hasSubflags()) { + // Fire events for all subflags as well + flag.getSubflags().forEach(subflag -> Bukkit.getPluginManager().callEvent(new FlagWorldSettingChangeEvent(user.getWorld(), user.getUniqueId(), subflag, subflag.isSetForWorld(user.getWorld())))); + } } // Save world settings diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Island.java b/src/main/java/world/bentobox/bentobox/database/objects/Island.java index 57554ada4..a2273e16f 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Island.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Island.java @@ -1,6 +1,7 @@ package world.bentobox.bentobox.database.objects; import java.io.IOException; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.EnumMap; import java.util.HashMap; @@ -823,11 +824,28 @@ public void setCreatedDate(long createdDate){ /** * Set the Island Guard flag rank + * This method affects subflags (if the given flag is a parent flag) * @param flag - flag * @param value - Use RanksManager settings, e.g. RanksManager.MEMBER */ - public void setFlag(Flag flag, int value){ + public void setFlag(Flag flag, int value) { + setFlag(flag, value, true); + } + + /** + * Set the Island Guard flag rank + * Also specify whether subflags are affected by this method call + * @param flag - flag + * @param value - Use RanksManager settings, e.g. RanksManager.MEMBER + * @param doSubflags - whether to set subflags + */ + public void setFlag(Flag flag, int value, boolean doSubflags) { flags.put(flag, value); + // Subflag support + if (doSubflags && flag.hasSubflags()) { + // Ensure that a subflag isn't a subflag of itself or else we're in trouble! + flag.getSubflags().forEach(subflag -> setFlag(subflag, value, true)); + } setChanged(); } @@ -1075,7 +1093,14 @@ public boolean showInfo(User user) { // Fixes #getLastPlayed() returning 0 when it is the owner's first connection. long lastPlayed = (Bukkit.getServer().getOfflinePlayer(getOwner()).getLastPlayed() != 0) ? Bukkit.getServer().getOfflinePlayer(getOwner()).getLastPlayed() : Bukkit.getServer().getOfflinePlayer(getOwner()).getFirstPlayed(); - user.sendMessage("commands.admin.info.last-login","[date]", new Date(lastPlayed).toString()); + String formattedDate; + try { + String dateTimeFormat = plugin.getLocalesManager().get("commands.admin.info.last-login-date-time-format"); + formattedDate = new SimpleDateFormat(dateTimeFormat).format(new Date(lastPlayed)); + } catch (NullPointerException | IllegalArgumentException ignored) { + formattedDate = new Date(lastPlayed).toString(); + } + user.sendMessage("commands.admin.info.last-login","[date]", formattedDate); user.sendMessage("commands.admin.info.deaths", "[number]", String.valueOf(plugin.getPlayers().getDeaths(getWorld(), getOwner()))); String resets = String.valueOf(plugin.getPlayers().getResets(getWorld(), getOwner())); @@ -1124,23 +1149,50 @@ public void showMembers(User user) { /** * Toggles a settings flag + * This method affects subflags (if the given flag is a parent flag) * @param flag - flag */ public void toggleFlag(Flag flag) { + toggleFlag(flag, true); + } + + /** + * Toggles a settings flag + * Also specify whether subflags are affected by this method call + * @param flag - flag + */ + public void toggleFlag(Flag flag, boolean doSubflags) { + boolean newToggleValue = !isAllowed(flag); // Use for subflags if (flag.getType().equals(Flag.Type.SETTING) || flag.getType().equals(Flag.Type.WORLD_SETTING)) { - setSettingsFlag(flag, !isAllowed(flag)); + setSettingsFlag(flag, newToggleValue, doSubflags); } setChanged(); } /** * Sets the state of a settings flag + * This method affects subflags (if the given flag is a parent flag) * @param flag - flag * @param state - true or false */ public void setSettingsFlag(Flag flag, boolean state) { + setSettingsFlag(flag, state, true); + } + + /** + * Sets the state of a settings flag + * Also specify whether subflags are affected by this method call + * @param flag - flag + * @param state - true or false + */ + public void setSettingsFlag(Flag flag, boolean state, boolean doSubflags) { + int newState = state ? 1 : -1; if (flag.getType().equals(Flag.Type.SETTING) || flag.getType().equals(Flag.Type.WORLD_SETTING)) { - flags.put(flag, state ? 1 : -1); + flags.put(flag, newState); + if (doSubflags && flag.hasSubflags()) { + // If we have circular subflags or a flag is a subflag of itself we are in trouble! + flag.getSubflags().forEach(subflag -> setSettingsFlag(subflag, state, true)); + } } setChanged(); } diff --git a/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java b/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java index 29882ce21..bac2aa6b1 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/JoinLeaveListener.java @@ -102,6 +102,9 @@ public void onPlayerJoin(final PlayerJoinEvent event) { plugin.getIslands().getMaxMembers(i, RanksManager.TRUSTED_RANK); plugin.getIslands().getMaxHomes(i); }); + + // Add a player to the bStats cache. + plugin.getMetrics().ifPresent(bStats -> bStats.addPlayer(playerUUID)); } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java index 1fbd2e2af..faf4408ad 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListener.java @@ -95,7 +95,7 @@ else if (e.getItem().getType().name().endsWith("_SPAWN_EGG")) { private void checkClickedBlock(Event e, Player player, Location loc, Material type) { // Handle pots if (type.name().startsWith("POTTED")) { - checkIsland(e, player, loc, Flags.CONTAINER); + checkIsland(e, player, loc, Flags.FLOWER_POT); return; } if (Tag.ANVIL.isTagged(type)) { @@ -115,7 +115,7 @@ private void checkClickedBlock(Event e, Player player, Location loc, Material ty return; } if (Tag.SHULKER_BOXES.isTagged(type)) { - checkIsland(e, player, loc, Flags.CONTAINER); + checkIsland(e, player, loc, Flags.SHULKER_BOX); return; } if (Tag.TRAPDOORS.isTagged(type)) { @@ -136,12 +136,20 @@ private void checkClickedBlock(Event e, Player player, Location loc, Material ty checkIsland(e, player, loc, Flags.HIVE); break; case BARREL: + checkIsland(e, player, loc, Flags.BARREL); + break; case CHEST: case CHEST_MINECART: + checkIsland(e, player, loc, Flags.CHEST); + break; case TRAPPED_CHEST: + checkIsland(e, player, loc, Flags.TRAPPED_CHEST); + break; case FLOWER_POT: + checkIsland(e, player, loc, Flags.FLOWER_POT); + break; case COMPOSTER: - checkIsland(e, player, loc, Flags.CONTAINER); + checkIsland(e, player, loc, Flags.COMPOSTER); break; case DISPENSER: checkIsland(e, player, loc, Flags.DISPENSER); diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java index 2cd31f7a2..f9e5275be 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/InventoryListener.java @@ -1,15 +1,20 @@ package world.bentobox.bentobox.listeners.flags.protection; +import org.bukkit.Material; +import org.bukkit.block.Barrel; import org.bukkit.block.Beacon; import org.bukkit.block.BrewingStand; +import org.bukkit.block.Chest; import org.bukkit.block.Dispenser; import org.bukkit.block.Dropper; import org.bukkit.block.Furnace; import org.bukkit.block.Hopper; +import org.bukkit.block.ShulkerBox; import org.bukkit.entity.Animals; import org.bukkit.entity.NPC; import org.bukkit.entity.Player; import org.bukkit.entity.minecart.HopperMinecart; +import org.bukkit.entity.minecart.StorageMinecart; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.inventory.InventoryClickEvent; @@ -61,6 +66,29 @@ else if (inventoryHolder instanceof Beacon) { else if (inventoryHolder instanceof NPC) { checkIsland(e, player, e.getInventory().getLocation(), Flags.TRADING); } + else if (inventoryHolder instanceof Barrel) { + checkIsland(e, player, e.getInventory().getLocation(), Flags.BARREL); + } + else if (inventoryHolder instanceof ShulkerBox) { + checkIsland(e, player, e.getInventory().getLocation(), Flags.SHULKER_BOX); + } + else if (inventoryHolder instanceof Chest) { + // To differentiate between a Chest and a Trapped Chest we need to get the Block corresponding to the inventory + Chest chestInventoryHolder = (Chest) inventoryHolder; + try { + if (chestInventoryHolder.getType() == Material.TRAPPED_CHEST) { + checkIsland(e, player, e.getInventory().getLocation(), Flags.TRAPPED_CHEST); + } else { + checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST); + } + } catch (IllegalStateException ignored) { + // Thrown if the Chest corresponds to a block that isn't placed (how did we get here?) + checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST); + } + } + else if (inventoryHolder instanceof StorageMinecart) { + checkIsland(e, player, e.getInventory().getLocation(), Flags.CHEST); + } else if (!(inventoryHolder instanceof Player)) { // All other containers checkIsland(e, player, e.getInventory().getLocation(), Flags.CONTAINER); diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/VisitorKeepInventoryListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/VisitorKeepInventoryListener.java new file mode 100644 index 000000000..e6ef8b142 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/worldsettings/VisitorKeepInventoryListener.java @@ -0,0 +1,39 @@ +package world.bentobox.bentobox.listeners.flags.worldsettings; + +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.PlayerDeathEvent; +import world.bentobox.bentobox.api.flags.FlagListener; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.lists.Flags; +import world.bentobox.bentobox.util.Util; + +import java.util.Optional; + +/** + * Prevents visitors from losing their items if they + * die on an island in which they are a visitor. + * Handles {@link world.bentobox.bentobox.lists.Flags#VISITOR_KEEP_INVENTORY}. + * @author jstnf + * @since 1.17.0 + */ +public class VisitorKeepInventoryListener extends FlagListener { + + @EventHandler (priority = EventPriority.LOW, ignoreCancelled = true) + public void onVisitorDeath(PlayerDeathEvent e) { + World world = Util.getWorld(e.getEntity().getWorld()); + if (!getIWM().inWorld(world) || !Flags.VISITOR_KEEP_INVENTORY.isSetForWorld(world)) { + // If the player dies outside of the island world, don't do anything + return; + } + + Optional island = getIslands().getProtectedIslandAt(e.getEntity().getLocation()); + if (island.isPresent() && !island.get().getMemberSet().contains(e.getEntity().getUniqueId())) { + e.setKeepInventory(true); + e.setKeepLevel(true); + e.getDrops().clear(); + e.setDroppedExp(0); + } + } +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/lists/Flags.java b/src/main/java/world/bentobox/bentobox/lists/Flags.java index a9d07fbce..057082afe 100644 --- a/src/main/java/world/bentobox/bentobox/lists/Flags.java +++ b/src/main/java/world/bentobox/bentobox/lists/Flags.java @@ -62,6 +62,7 @@ import world.bentobox.bentobox.listeners.flags.worldsettings.RemoveMobsListener; import world.bentobox.bentobox.listeners.flags.worldsettings.SpawnerSpawnEggsListener; import world.bentobox.bentobox.listeners.flags.worldsettings.TreesGrowingOutsideRangeListener; +import world.bentobox.bentobox.listeners.flags.worldsettings.VisitorKeepInventoryListener; import world.bentobox.bentobox.listeners.flags.worldsettings.WitherListener; import world.bentobox.bentobox.managers.RanksManager; import world.bentobox.bentobox.util.Util; @@ -110,7 +111,14 @@ private Flags() {} public static final Flag BEACON = new Flag.Builder("BEACON", Material.BEACON).build(); public static final Flag BED = new Flag.Builder("BED", Material.RED_BED).build(); public static final Flag BREWING = new Flag.Builder("BREWING", Material.BREWING_STAND).mode(Flag.Mode.ADVANCED).build(); - public static final Flag CONTAINER = new Flag.Builder("CONTAINER", Material.CHEST).mode(Flag.Mode.BASIC).build(); + // START CONTAINER split + public static final Flag CHEST = new Flag.Builder("CHEST", Material.CHEST).mode(Flag.Mode.ADVANCED).build(); + public static final Flag BARREL = new Flag.Builder("BARREL", Material.BARREL).mode(Flag.Mode.ADVANCED).build(); + public static final Flag COMPOSTER = new Flag.Builder("COMPOSTER", Material.COMPOSTER).mode(Flag.Mode.ADVANCED).build(); + public static final Flag FLOWER_POT = new Flag.Builder("FLOWER_POT", Material.FLOWER_POT).mode(Flag.Mode.ADVANCED).build(); + public static final Flag SHULKER_BOX = new Flag.Builder("SHULKER_BOX", Material.SHULKER_BOX).mode(Flag.Mode.ADVANCED).build(); + public static final Flag TRAPPED_CHEST = new Flag.Builder("TRAPPED_CHEST", Material.TRAPPED_CHEST).mode(Flag.Mode.ADVANCED).build(); + // END CONTAINER split public static final Flag DISPENSER = new Flag.Builder("DISPENSER", Material.DISPENSER).mode(Flag.Mode.ADVANCED).build(); public static final Flag DROPPER = new Flag.Builder("DROPPER", Material.DROPPER).mode(Flag.Mode.ADVANCED).build(); public static final Flag HOPPER = new Flag.Builder("HOPPER", Material.HOPPER).mode(Flag.Mode.ADVANCED).build(); @@ -129,6 +137,9 @@ private Flags() {} public static final Flag ITEM_FRAME = new Flag.Builder("ITEM_FRAME", Material.ITEM_FRAME).mode(Flag.Mode.ADVANCED).build(); public static final Flag CAKE = new Flag.Builder("CAKE", Material.CAKE).build(); public static final Flag HIVE = new Flag.Builder("HIVE", Material.HONEY_BOTTLE).type(Type.PROTECTION).build(); + public static final Flag CONTAINER = new Flag.Builder("CONTAINER", Material.CHEST).mode(Flag.Mode.BASIC) + .subflags(BREWING, BARREL, CHEST, COMPOSTER, FLOWER_POT, SHULKER_BOX, TRAPPED_CHEST, FURNACE, JUKEBOX, DISPENSER, DROPPER, HOPPER, ITEM_FRAME, HIVE) + .build(); /** * Prevents players from interacting with the Dragon Egg. @@ -526,6 +537,13 @@ private Flags() {} */ public static final Flag PETS_STAY_AT_HOME = new Flag.Builder("PETS_STAY_AT_HOME", Material.TROPICAL_FISH).listener(new PetTeleportListener()).type(Type.WORLD_SETTING).defaultSetting(true).build(); + /** + * Toggles whether island visitors keep their items if they die on another player's island. + * @since 1.17.0 + * @see VisitorKeepInventoryListener + */ + public static final Flag VISITOR_KEEP_INVENTORY = new Flag.Builder("VISITOR_KEEP_INVENTORY", Material.TOTEM_OF_UNDYING).listener(new VisitorKeepInventoryListener()).type(Type.WORLD_SETTING).defaultSetting(false).build(); + /** * Provides a list of all the Flag instances contained in this class using reflection. * Deprecated Flags are ignored. diff --git a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java index 9bf1a132c..54b365fab 100644 --- a/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java +++ b/src/main/java/world/bentobox/bentobox/versions/ServerCompatibility.java @@ -183,7 +183,11 @@ public enum ServerVersion { /** * @since 1.17.0 */ - V1_17(Compatibility.COMPATIBLE) + V1_17(Compatibility.COMPATIBLE), + /** + * @since 1.17.1 + */ + V1_17_1(Compatibility.COMPATIBLE) ; private Compatibility compatibility; diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 10578d84e..9dc3d7cee 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -188,6 +188,7 @@ commands: island-uuid: "UUID: [uuid]" owner: "Owner: [owner] ([uuid])" last-login: "Last login: [date]" + last-login-date-time-format: "EEE MMM dd HH:mm:ss zzz yyyy" deaths: "Deaths: [number]" resets-left: "Resets: [number] (Max: [total])" team-members-title: "Team members:" @@ -787,15 +788,45 @@ protection: name: "Cakes" hint: "Cake eating disabled" CONTAINER: - name: "Containers" + name: "All containers" description: |- - &a Toggle interaction with chests, - &a shulker boxes and flower pots, - &a composters and barrels. + &a Toggle interaction with all containers. + &a Includes: Barrel, bee hive, brewing stand, + &a chest, composter, dispenser, dropper, + &a flower pot, furnace, hopper, item frame, + &a jukebox, minecart chest, shulker box, + &a trapped chest. - &7 Other containers are handled - &7 by dedicated flags. + &7 Changing individual settings overrides + &7 this flag. hint: "Container access disabled" + CHEST: + name: "Chests and minecart chests" + description: |- + &a Toggle interaction with chests + &a and chest minecarts. + &a (does not include trapped chests) + hint: "Chest access disabled" + BARREL: + name: "Barrels" + description: "Toggle barrel interaction" + hint: "Barrel access disabled" + COMPOSTER: + name: "Composters" + description: "Toggle composter interaction" + hint: "Composter interaction disabled" + FLOWER_POT: + name: "Flower pots" + description: "Toggle flower pot interaction" + hint: "Flower pot interaction disabled" + SHULKER_BOX: + name: "Shulker boxes" + description: "Toggle shulker box interaction" + hint: "Shulker box access disabled" + TRAPPED_CHEST: + name: "Trapped chests" + description: "Toggle trapped chest interaction" + hint: "Trapped chest access disabled" DISPENSER: name: "Dispensers" description: "Toggle dispenser interaction" @@ -1271,6 +1302,15 @@ protection: &a back to their island using commands &a if they are falling. hint: "&c You cannot do that while falling." + VISITOR_KEEP_INVENTORY: + name: "Visitors keep inventory on death" + description: |- + &a Prevent players from losing their + &a items and experience if they die on + &a an island in which they are a visitor. + &a + &a Island members still lose their items + &a if they die on their own island! WITHER_DAMAGE: name: "Toggle wither damage" description: |- diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a07ee8626..f080ab77f 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -24,6 +24,7 @@ softdepend: - LangUtils - WildStacker - LuckPerms + - HolographicDisplays permissions: bentobox.admin: diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java index c86dae48a..ad3cac559 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/BlockInteractionListenerTest.java @@ -70,14 +70,14 @@ private void setFlags() { when(Tag.BEDS.isTagged(Material.WHITE_BED)).thenReturn(true); clickedBlocks.put(Material.BREWING_STAND, Flags.BREWING); clickedBlocks.put(Material.CAULDRON, Flags.BREWING); - clickedBlocks.put(Material.BARREL, Flags.CONTAINER); - clickedBlocks.put(Material.CHEST, Flags.CONTAINER); - clickedBlocks.put(Material.CHEST_MINECART, Flags.CONTAINER); - clickedBlocks.put(Material.TRAPPED_CHEST, Flags.CONTAINER); - clickedBlocks.put(Material.SHULKER_BOX, Flags.CONTAINER); + clickedBlocks.put(Material.BARREL, Flags.BARREL); + clickedBlocks.put(Material.CHEST, Flags.CHEST); + clickedBlocks.put(Material.CHEST_MINECART, Flags.CHEST); + clickedBlocks.put(Material.TRAPPED_CHEST, Flags.TRAPPED_CHEST); + clickedBlocks.put(Material.SHULKER_BOX, Flags.SHULKER_BOX); when(Tag.SHULKER_BOXES.isTagged(Material.SHULKER_BOX)).thenReturn(true); - clickedBlocks.put(Material.FLOWER_POT, Flags.CONTAINER); - clickedBlocks.put(Material.COMPOSTER, Flags.CONTAINER); + clickedBlocks.put(Material.FLOWER_POT, Flags.FLOWER_POT); + clickedBlocks.put(Material.COMPOSTER, Flags.COMPOSTER); clickedBlocks.put(Material.DISPENSER, Flags.DISPENSER); clickedBlocks.put(Material.DROPPER, Flags.DROPPER); clickedBlocks.put(Material.HOPPER, Flags.HOPPER); diff --git a/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java index fad50de4d..677202abd 100644 --- a/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/FlagsManagerTest.java @@ -46,7 +46,7 @@ public class FlagsManagerTest { /** * Update this value if the number of registered listeners changes */ - private static final int NUMBER_OF_LISTENERS = 49; + private static final int NUMBER_OF_LISTENERS = 50; @Mock private BentoBox plugin; @Mock