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