diff --git a/pom.xml b/pom.xml index a685afaa0..c4b5ccd78 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ -LOCAL - 2.5.2 + 2.5.3 bentobox-world https://sonarcloud.io ${project.basedir}/lib @@ -228,6 +228,13 @@ 3.11.1 test + + + org.awaitility + awaitility + 4.2.2 + test + org.spigotmc diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java index 75a7c07a8..650d5e4a5 100644 --- a/src/main/java/world/bentobox/bentobox/BentoBox.java +++ b/src/main/java/world/bentobox/bentobox/BentoBox.java @@ -324,6 +324,8 @@ public void onDisable() { // Stop all async database tasks shutdown = true; + HeadGetter.shutdown(); + if (addonsManager != null) { addonsManager.disableAddons(); } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java index 6cb702ea7..0e9b55be8 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamCommand.java @@ -140,17 +140,30 @@ public void addInvite(TeamInvite.Type type, @NonNull UUID inviter, @NonNull UUID } /** - * Check if a player has been invited + * Check if a player has been invited - validates any invite that may be in the system * @param invitee - UUID of invitee to check * @return true if invited, false if not * @since 1.8.0 */ public boolean isInvited(@NonNull UUID invitee) { - return handler.objectExists(invitee.toString()); + boolean valid = false; + if (handler.objectExists(invitee.toString())) { + @Nullable + TeamInvite invite = getInvite(invitee); + valid = getIslands().getIslandById(invite.getUniqueId()).map(island -> island.isOwned() // Still owned by someone + && !island.isDeleted() // Not deleted + && island.getMemberSet().contains(invite.getInviter()) // the inviter is still a member of the island + ).orElse(false); + if (!valid) { + // Remove invite + handler.deleteObject(invite); + } + } + return valid; } /** - * Get whoever invited invitee + * Get whoever invited invitee. * @param invitee - uuid * @return UUID of inviter, or null if invitee has not been invited * @since 1.8.0 diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java index 3ba3013f1..9244508c7 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteAcceptCommand.java @@ -48,6 +48,7 @@ public boolean canExecute(User user, String label, List args) { UUID prospectiveOwnerUUID = itc.getInviter(playerUUID); if (prospectiveOwnerUUID == null) { user.sendMessage(INVALID_INVITE); + itc.removeInvite(playerUUID); return false; } TeamInvite invite = itc.getInvite(playerUUID); @@ -65,6 +66,7 @@ public boolean canExecute(User user, String label, List args) { if (getIWM().getWorldSettings(getWorld()).isDisallowTeamMemberIslands() && getIslands().inTeam(getWorld(), playerUUID)) { user.sendMessage("commands.island.team.invite.errors.you-already-are-in-team"); + itc.removeInvite(playerUUID); return false; } // Fire event so add-ons can run commands, etc. diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java index 82d2bf4dd..eee876df3 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java @@ -61,7 +61,7 @@ public boolean canExecute(User user, String label, List args) { if (args.size() != 1) { new IslandTeamInviteGUI(itc, true, island).build(user); - return true; + return false; } int rank = Objects.requireNonNull(island).getRank(user); @@ -153,6 +153,7 @@ public boolean execute(User user, String label, List args) { Island island = getIslands().getIsland(getWorld(), user.getUniqueId()); if (island == null) { user.sendMessage("general.errors.no-island"); + invitedPlayer = null; return false; } // Fire event so add-ons can run commands, etc. @@ -162,6 +163,7 @@ public boolean execute(User user, String label, List args) { .involvedPlayer(invitedPlayer.getUniqueId()) .build(); if (e.getNewEvent().map(IslandBaseEvent::isCancelled).orElse(e.isCancelled())) { + invitedPlayer = null; return false; } // Put the invited player (key) onto the list with inviter (value) @@ -175,6 +177,7 @@ public boolean execute(User user, String label, List args) { && getIslands().hasIsland(getWorld(), invitedPlayer.getUniqueId())) { invitedPlayer.sendMessage("commands.island.team.invite.you-will-lose-your-island"); } + invitedPlayer = null; return true; } diff --git a/src/main/java/world/bentobox/bentobox/api/user/User.java b/src/main/java/world/bentobox/bentobox/api/user/User.java index f9508eb37..8e7c32260 100644 --- a/src/main/java/world/bentobox/bentobox/api/user/User.java +++ b/src/main/java/world/bentobox/bentobox/api/user/User.java @@ -505,12 +505,6 @@ private String translate(String addonPrefix, String reference, String[] variable private String replacePrefixes(String translation, String[] variables) { for (String prefix : plugin.getLocalesManager().getAvailablePrefixes(this)) { String prefixTranslation = getTranslation("prefixes." + prefix); - // Replace the [gamemode] text variable - prefixTranslation = prefixTranslation.replace("[gamemode]", - addon != null ? addon.getDescription().getName() : "[gamemode]"); - // Replace the [friendly_name] text variable - prefixTranslation = prefixTranslation.replace("[friendly_name]", - isPlayer() ? plugin.getIWM().getFriendlyName(getWorld()) : "[friendly_name]"); // Replace the prefix in the actual message translation = translation.replace("[prefix_" + prefix + "]", prefixTranslation); @@ -530,6 +524,17 @@ private String replacePrefixes(String translation, String[] variables) { if (player != null) { translation = plugin.getPlaceholdersManager().replacePlaceholders(player, translation); } + + // Replace game mode and friendly name in general + // Replace the [gamemode] text variable + if (addon != null && addon.getDescription() != null) { + translation = translation.replace("[gamemode]", addon.getDescription().getName()); + } + if (player != null && player.getWorld() != null) { + // Replace the [friendly_name] text variable + translation = translation.replace("[friendly_name]", + isPlayer() ? plugin.getIWM().getFriendlyName(getWorld()) : "[friendly_name]"); + } return translation; } diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java index a97dcb918..f80aa0d85 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/BreakBlocksListener.java @@ -22,7 +22,6 @@ import com.google.common.base.Enums; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.lists.Flags; @@ -51,6 +50,7 @@ public void onBlockBreak(final BlockBreakEvent e) { || m == Material.SWEET_BERRY_BUSH || m == Material.BAMBOO || m == Material.NETHER_WART + || m == Material.CACTUS ) { this.checkIsland(e, p, l, Flags.HARVEST); } else { diff --git a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListener.java b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListener.java index 406e30202..39c6a1625 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListener.java @@ -80,7 +80,7 @@ else if (e.getRightClicked() instanceof Boat) this.checkIsland(e, p, l, Flags.BOAT); } } - else if (e.getRightClicked() instanceof Villager || e.getRightClicked() instanceof WanderingTrader) + else if (e.getRightClicked() instanceof Villager && !(e.getRightClicked() instanceof WanderingTrader)) { // Villager trading // Check naming and check trading diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 7d7364365..5b11937f0 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -1209,6 +1209,7 @@ public boolean isAtSpawn(Location playerLoc) { */ public void setSpawn(@NonNull Island spawn) { if (spawn.getWorld() != null) { + spawn.setSpawn(true); spawns.put(Util.getWorld(spawn.getWorld()), spawn); // Tell other servers MultiLib.notify("bentobox-setspawn", spawn.getWorld().getUID().toString() + "," + spawn.getUniqueId()); @@ -1223,9 +1224,12 @@ public void setSpawn(@NonNull Island spawn) { * @since 1.8.0 */ public void clearSpawn(World world) { - spawns.remove(world); - // Tell other servers - MultiLib.notify("bentobox-setspawn", world.getUID().toString()); + if (spawns.containsKey(world)) { + spawns.get(world).setSpawn(false); + spawns.remove(world); + // Tell other servers + MultiLib.notify("bentobox-setspawn", world.getUID().toString()); + } } /** diff --git a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java index dd5dd9bb0..a92f4c2e9 100644 --- a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java @@ -4,12 +4,11 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import org.bukkit.World; import org.bukkit.entity.Player; @@ -23,6 +22,7 @@ import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.database.objects.Names; import world.bentobox.bentobox.database.objects.Players; +import world.bentobox.bentobox.util.ExpiringMap; import world.bentobox.bentobox.util.Util; public class PlayersManager { @@ -30,7 +30,7 @@ public class PlayersManager { private final BentoBox plugin; private Database handler; private final Database names; - private final Map playerCache = new ConcurrentHashMap<>(); + private final ExpiringMap playerCache = new ExpiringMap<>(2, TimeUnit.HOURS); private final @NonNull List nameCache; private final Set inTeleport; // this needs databasing @@ -61,6 +61,7 @@ public void setHandler(Database handler) { public void shutdown(){ handler.close(); + playerCache.shutdown(); } /** diff --git a/src/main/java/world/bentobox/bentobox/util/ExpiringMap.java b/src/main/java/world/bentobox/bentobox/util/ExpiringMap.java new file mode 100644 index 000000000..fec45e7c9 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/util/ExpiringMap.java @@ -0,0 +1,271 @@ +package world.bentobox.bentobox.util; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * A {@code ExpiringMap} is a map implementation that automatically removes entries after a + * specified period of time. The expiration time is specified when the map is created and + * applies to all entries put into the map. It is thread-safe and provides similar + * functionality to {@code HashMap} with the added feature of automatic expiration of entries. + * + *

This class makes use of a {@link ConcurrentHashMap} for thread safety and a + * {@link ScheduledExecutorService} to handle the expiration of entries. All operations are + * thread-safe. + * + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public class ExpiringMap implements Map { + private final Map map; + private final ScheduledExecutorService scheduler; + private final long expirationTime; + + /** + * Constructs an empty {@code ExpiringMap} with the specified expiration time for entries. + * + * @param expirationTime the time after which entries should expire, in the specified time unit + * @param timeUnit the time unit for the {@code expirationTime} parameter + * @throws IllegalArgumentException if {@code expirationTime} is less than or equal to zero + * @throws NullPointerException if {@code timeUnit} is null + */ + public ExpiringMap(long expirationTime, TimeUnit timeUnit) { + if (expirationTime <= 0) { + throw new IllegalArgumentException("Expiration time must be greater than zero."); + } + if (timeUnit == null) { + throw new NullPointerException("TimeUnit cannot be null."); + } + this.map = new ConcurrentHashMap<>(); + this.scheduler = Executors.newSingleThreadScheduledExecutor(); + this.expirationTime = timeUnit.toMillis(expirationTime); + } + + /** + * Associates the specified value with the specified key in this map. If the map + * previously contained a mapping for the key, the old value is replaced. + * The entry will automatically be removed after the specified expiration time. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @throws NullPointerException if the specified key or value is null + * @return the previous value associated with {@code key}, or {@code null} if there was no mapping for {@code key}. + */ + @Override + public V put(K key, V value) { + if (key == null || value == null) { + throw new NullPointerException("Key and Value cannot be null."); + } + V oldValue = map.put(key, value); + scheduleRemoval(key); + return oldValue; + } + + /** + * Returns the value to which the specified key is mapped, or {@code null} if this map contains + * no mapping for the key. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or {@code null} if this map contains no mapping for the key + * @throws NullPointerException if the specified key is null + */ + @Override + public V get(Object key) { + if (key == null) { + throw new NullPointerException("Key cannot be null."); + } + return map.get(key); + } + + /** + * Removes the mapping for a key from this map if it is present. + * + * @param key key whose mapping is to be removed from the map + * @return the previous value associated with {@code key}, or {@code null} if there was no mapping for {@code key}. + * (A {@code null} return can also indicate that the map previously associated {@code null} with {@code key}.) + * @throws NullPointerException if the specified key is null + */ + @Override + public V remove(Object key) { + if (key == null) { + throw new NullPointerException("Key cannot be null."); + } + return map.remove(key); + } + + /** + * Returns {@code true} if this map contains a mapping for the specified key. + * + * @param key key whose presence in this map is to be tested + * @return {@code true} if this map contains a mapping for the specified key + * @throws NullPointerException if the specified key is null + */ + @Override + public boolean containsKey(Object key) { + if (key == null) { + throw new NullPointerException("Key cannot be null."); + } + return map.containsKey(key); + } + + /** + * Returns {@code true} if this map maps one or more keys to the specified value. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if this map maps one or more keys to the specified value + * @throws NullPointerException if the specified value is null + */ + @Override + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException("Value cannot be null."); + } + return map.containsValue(value); + } + + /** + * Returns the number of key-value mappings in this map. If the map contains more than + * {@code Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}. + * + * @return the number of key-value mappings in this map + */ + @Override + public int size() { + return map.size(); + } + + /** + * Returns {@code true} if this map contains no key-value mappings. + * + * @return {@code true} if this map contains no key-value mappings + */ + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * Copies all of the mappings from the specified map to this map. The effect of this call is + * equivalent to that of calling {@link #put(Object, Object) put(k, v)} on this map once + * for each mapping from key {@code k} to value {@code v} in the specified map. The behavior + * of this operation is undefined if the specified map is modified while the operation is in progress. + * + * @param m mappings to be stored in this map + * @throws NullPointerException if the specified map is null, or if any key or value in the specified map is null + */ + @Override + public void putAll(Map m) { + if (m == null) { + throw new NullPointerException("The specified map cannot be null."); + } + for (Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + /** + * Removes all of the mappings from this map. The map will be empty after this call returns. + */ + @Override + public void clear() { + map.clear(); + } + + /** + * Returns a {@link Set} view of the keys contained in this map. The set is backed by the map, + * so changes to the map are reflected in the set, and vice-versa. If the map is modified while + * an iteration over the set is in progress, the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding mapping from the map, via the + * {@code Iterator.remove}, {@code Set.remove}, {@code removeAll}, {@code retainAll}, and + * {@code clear} operations. It does not support the {@code add} or {@code addAll} operations. + * + * @return a set view of the keys contained in this map + */ + @Override + public Set keySet() { + return map.keySet(); + } + + /** + * Returns a {@link Collection} view of the values contained in this map. The collection is + * backed by the map, so changes to the map are reflected in the collection, and vice-versa. + * If the map is modified while an iteration over the collection is in progress, the results + * of the iteration are undefined. The collection supports element removal, which removes + * the corresponding mapping from the map, via the {@code Iterator.remove}, {@code Collection.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} operations. It does not support the + * {@code add} or {@code addAll} operations. + * + * @return a collection view of the values contained in this map + */ + @Override + public Collection values() { + return map.values(); + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. The set is backed by the map, + * so changes to the map are reflected in the set, and vice-versa. If the map is modified while + * an iteration over the set is in progress, the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding mapping from the map, via the + * {@code Iterator.remove}, {@code Set.remove}, {@code removeAll}, {@code retainAll}, and + * {@code clear} operations. It does not support the {@code add} or {@code addAll} operations. + * + * @return a set view of the mappings contained in this map + */ + @Override + public Set> entrySet() { + return map.entrySet(); + } + + /** + * If the specified key is not already associated with a value, attempts to compute its + * value using the given mapping function and enters it into this map unless {@code null}. + * + *

If the mapping function returns {@code null}, no mapping is recorded. If the mapping + * function itself throws an (unchecked) exception, the exception is rethrown, and no mapping + * is recorded. The computed value is set to expire after the specified expiration time. + * + * @param key key with which the specified value is to be associated + * @param mappingFunction the function to compute a value + * @return the current (existing or computed) value associated with the specified key, or {@code null} if the computed value is {@code null} + * @throws NullPointerException if the specified key or mappingFunction is null + */ + public V computeIfAbsent(K key, Function mappingFunction) { + if (key == null || mappingFunction == null) { + throw new NullPointerException("Key and mappingFunction cannot be null."); + } + return map.computeIfAbsent(key, k -> { + V value = mappingFunction.apply(k); + scheduleRemoval(k); + return value; + }); + } + + /** + * Schedules the removal of the specified key from this map after the expiration time. + * + * @param key key whose mapping is to be removed from the map after the expiration time + */ + private void scheduleRemoval(final K key) { + scheduler.schedule(() -> map.remove(key), expirationTime, TimeUnit.MILLISECONDS); + } + + /** + * Shuts down the {@code ScheduledExecutorService} used for scheduling the removal of + * entries. This method should be called to release resources once the {@code ExpiringMap} + * is no longer needed. + * + *

Once the executor is shut down, no more entries will be automatically removed. + * It is the user's responsibility to ensure that the {@code shutdown} method is called. + */ + public void shutdown() { + scheduler.shutdown(); + } + +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/util/heads/HeadGetter.java b/src/main/java/world/bentobox/bentobox/util/heads/HeadGetter.java index e45e199a7..813a5ce1f 100644 --- a/src/main/java/world/bentobox/bentobox/util/heads/HeadGetter.java +++ b/src/main/java/world/bentobox/bentobox/util/heads/HeadGetter.java @@ -5,13 +5,12 @@ import java.net.HttpURLConnection; import java.net.URL; import java.util.Base64; -import java.util.HashMap; import java.util.HashSet; -import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.UUID; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.bukkit.Bukkit; @@ -25,6 +24,7 @@ import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.util.ExpiringMap; import world.bentobox.bentobox.util.Pair; /** @@ -36,7 +36,7 @@ public class HeadGetter { /** * Local cache for storing player heads. */ - private static final Map cachedHeads = new HashMap<>(); + private static final ExpiringMap cachedHeads = new ExpiringMap<>(1, TimeUnit.HOURS); /** * Local cache for storing requested names and items which must be updated. @@ -46,7 +46,8 @@ public class HeadGetter { /** * Requesters of player heads. */ - private static final Map> headRequesters = new HashMap<>(); + private static final ExpiringMap> headRequesters = new ExpiringMap<>(10, + TimeUnit.SECONDS); private static final String TEXTURES = "textures"; @@ -65,6 +66,14 @@ public HeadGetter(BentoBox plugin) { this.runPlayerHeadGetter(); } + /** + * Shutdown the schedulers + */ + public static void shutdown() { + cachedHeads.shutdown(); + headRequesters.shutdown(); + } + /** * @param panelItem - head to update * @param requester - callback class diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index c0c94254f..09f8e3d17 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -138,7 +138,13 @@ commands: parameters: "" description: überträgt das Insel-Eigentum auf den Spieler already-owner: "&c [name] ist bereits der Besitzer dieser Insel!" + must-be-on-island: "&c Sie müssen auf der Insel sein, um den Besitzer festzulegen" + confirmation: "&a Möchten Sie [name] wirklich als Eigentümer der Insel in + [xyz] festlegen?" success: "&b [name]&a ist jetzt der Besitzer dieser Insel." + extra-islands: "&c Warnung: Dieser Spieler besitzt jetzt [number] Inseln. + Das sind mehr als durch die Einstellungen oder Berechtigungen erlaubt ist: + [max]." range: description: Admin Insel Bereichsbefehl invalid-value: @@ -158,7 +164,7 @@ commands: Grüne Partikel &f zeigen den voreingestellten Schutzbereich an, wenn der Inselschutzbereich davon abweicht. showing: "&2 Anzeigen von Reichweite Indikatoren" set: - parameters: " " + parameters: " [Inselstandort]" description: legt den geschützten Inselbereich fest success: "&a Inselschutzbereich einstellen auf &b [number]&a ." reset: @@ -167,12 +173,12 @@ commands: success: "& a Setzen Sie den Inselschutzbereich auf & b [number] & a zurück." add: description: Erhöht den Schutzbereich der Insel - parameters: " " + parameters: " [Inselstandort]" success: "&a Erfolgreiche Erhöhung von &b [name]&a 's geschützten Bereich der Insel auf &b [total] &7 (&b +[number]&7 )&a ." remove: description: Verringert den Schutzbereich der Insel - parameters: " " + parameters: " [Inselstandort]" success: "&a Erfolgreich reduziert &b [name]&a 's geschützten Bereich der Insel auf&b [total] &7 (&b -[number]&7 )&a ." register: @@ -191,6 +197,11 @@ commands: parameters: " [x,y,z]" description: Besitzer von Insel entfernen, aber Inselblöcke behalten unregistered-island: "&a Unregistrierter Spieler von der Insel bei [xyz]." + errors: + unknown-island-location: "&c Unbekannter Inselstandort" + specify-island-location: "&c Gib denn Inselstandort in x,y,z Format an" + player-has-more-than-one-island: "&c Spieler hat mehr als eine Insel. Gib + an welche" info: parameters: "" description: Informationen über deinen Standort oder die Spielerinsel erhalten @@ -219,6 +230,7 @@ commands: banned-players: 'Gebannte Spieler:' banned-format: "&c [name]" unowned: "&c Frei" + bundle: "&a Blaupausenpaket zum Erstellen der Insel: &b [name]" switch: description: Schutzumgehung ein-/ausschalten op: "&c Ops können den Schutz immer umgehen. Deop um den Befehl zu benutzen." @@ -257,10 +269,13 @@ commands: reload: description: neu laden tp: - parameters: "" + parameters: " [Insel des Spielers]" description: zu einer Spielerinsel teleportieren manual: "&c Kein sicherer Warp gefunden! Manuell in die Nähe von &b [location] &c teleportieren und nachsehen" + tpuser: + parameters: " [Insel des Spielers]" + description: einen Spieler zur Insel eines anderen Spielers teleportieren getrank: parameters: "" description: den Rang eines Spielers auf seiner Insel erhalten @@ -406,6 +421,12 @@ commands: slot-instructions: |- &a Linksklick zum Erhöhen &a Rechtsklick zum Verringern + times: | + &a Maximale gleichzeitige Nutzung durch Spieler + &a Linksklick zum Erhöhen + &a Rechtsklick zum Verringern + unlimited-times: Unbegrenzt + maximum-times: Max. [number] Mal resetflags: parameters: "[flag]" description: Alle Inseln in der config.yml auf Standard-Flag-Einstellungen zurücksetzen @@ -498,6 +519,18 @@ commands: addons: "&6 Migrieren von Addons" class: "&6 Migration [description]" migrated: "&A migriert" + completed: "[prefix_bentobox]&a Abgeschlossen" + rank: + description: Ränge auflisten, hinzufügen oder entfernen + parameters: "&a [list | add | remove] [Rangreferenz] [Rangwert]" + add: + success: "&a [rank] mit Wert [number] hinzugefügt" + failure: "&c [rank] konnte nicht mit dem Wert [number] hinzugefügt werden. + Vielleicht ein Duplikat?" + remove: + success: "&a Entfernt [rank]" + failure: "&c [rank] konnte nicht entfernt werden. Unbekannter Rang." + list: "&a Die registrierten Ränge sind wie folgt:" confirmation: confirm: "&c Befehl innerhalb von &b [seconds]s&c zur Bestätigung erneut eingeben." previous-request-cancelled: "&6 Vorherige Bestätigungsanforderung abgebrochen." @@ -533,6 +566,9 @@ commands: einen Admin." creating-island: "&a Einen Ort für deine Insel finden..." you-cannot-make: "&c Du kannst keine weiteren Inseln erschaffen!" + max-uses: "&c Von dieser Art Inseln kann man keine weiteren machen!" + you-cannot-make-team: "&c Teammitglieder können keine Inseln in derselben Welt + wie ihre Teaminsel erstellen." pasting: estimated-time: "&a Geschätzte Zeit: &b [number] &a Sekunden." blocks: "&a Block für Block aufbauen: &b [number] &a Blöcke insgesamt..." @@ -600,18 +636,53 @@ commands: description: einen Heimatort umbenennen parameters: "[Heimatname]" enter-new-name: "&6 Geben Sie den neuen Namen ein" - already-exists: "&c Dieser Name existiert bereits, versuchen Sie es mit einem anderen Namen." + already-exists: "&c Dieser Name existiert bereits, versuchen Sie es mit einem + anderen Namen." resetname: description: setze deinen Inselnamen zurück success: "&a Setzen Sie Ihren Inselnamen erfolgreich zurück." team: description: Dein Team verwalten + gui: + titles: + team-panel: Teammanagement + buttons: + status: + name: Status + description: Der Status des Teams + rank-filter: + name: Rangfilter + description: "&a Klicken Sie hier, um die Ränge zu wechseln" + invitation: Einladung + invite: + name: Spieler einladen + description: | + &a Spieler müssen sich in der + &a selben Welt wie Sie befinden, um + &a in der Liste angezeigt zu werden. + tips: + LEFT: + name: "&b Linksklick" + invite: "&a, um einen Spieler einzuladen" + RIGHT: + name: "&b Rechtsklick" + SHIFT_RIGHT: + name: "&b Umschalttaste Rechtsklick" + reject: "&a zum Ablehnen" + kick: "&a um Spieler rauszuwerfen" + leave: "&a verlässt das Team" + SHIFT_LEFT: + name: "&b Umschalttaste Linksklick" + accept: "&a zum Akzeptieren" + setowner: | + &a, um den Besitzer + &a für diesen Spieler festzulegen info: description: zeigt detaillierte Informationen über dein Team an member-layout: - online: '&a &l o &r &f [name]' - offline: '&c &l o &r &f [name] &7 ([last_seen])' - offline-not-last-seen: '&c &l o &r &f [name]' + online: "&a &l o &r &f [name]" + offline: "&c &l o &r &f [name] &7 ([last_seen])" + offline-not-last-seen: "&c &l o &r &f [name]" last-seen: layout: "&b [number] &7 [unit] vorher" days: Tage @@ -631,8 +702,10 @@ commands: already-has-rank: "&c Der Spieler hat bereits einen Rang!" you-are-a-coop-member: "&2 Du wurdest von [name] gecooped" success: "&a Du hast &b [name] gecooped." - name-has-invited-you: "&a [name] hat dich eingeladen, ein Coop-Mitglied ihrer - Insel zu werden." + name-has-invited-you: | + &a [name] hat Sie eingeladen, + &a als Genossenschaftsmitglied + &a ihrer Insel beizutreten. uncoop: description: einen Coop-Rang von einem Spieler entfernen parameters: "" @@ -649,8 +722,10 @@ commands: description: Gib einem Spieler einen vertrauenswürdigen Rang auf deiner Insel parameters: "" trust-in-yourself: "&c Vertraue auf dich selbst!" - name-has-invited-you: "&a [name] hat dich eingeladen, ein vertrauenswürdiges - Mitglied ihrer Insel zu werden." + name-has-invited-you: | + &a [name] hat Sie eingeladen, + &a als vertrauenswürdiges Mitglied + &a ihrer Insel beizutreten. player-already-trusted: "&c Der Spieler ist bereits vertrauenswürdig!" you-are-trusted: "&2 Du bist vertrauenswürdig für &b [name]&a !" success: "&a Du vertraust &b [name]&a ." @@ -668,11 +743,38 @@ commands: description: einen Spieler auf deine Insel einladen invitation-sent: "&a Einladung gesendet an [name]" removing-invite: "&c Einladung entfernen" - name-has-invited-you: "&a [name] hat dich eingeladen ihrer Insel beizutreten." + name-has-invited-you: | + &a [name] hat Sie eingeladen, + &a ihrer Insel beizutreten. to-accept-or-reject: "&a Gib /[label] team accept um zu akzeptieren, oder /[label] team reject um abzulehnen" - you-will-lose-your-island: "&c WARNUNG! Du wirst deine Insel verlieren, wenn - du akzeptierst!" + you-will-lose-your-island: | + &c WARNUNG! Sie verlieren alle + &c Ihre Inseln, wenn Sie akzeptieren! + gui: + titles: + team-invite-panel: Spieler einladen + button: + already-invited: "&c Bereits eingeladen" + search: "&a Suche nach einem Spieler" + searching: | + &b Suche nach + &c [name] + enter-name: "&a Namen eingeben:" + tips: + LEFT: + name: "&b Linksklick" + search: "&a Geben Sie den Namen des Spielers ein" + back: "&a Zurück" + invite: | + &a, um einen Spieler einzuladen + &a, um deinem Team beizutreten + RIGHT: + name: "&b Rechtsklick" + coop: "&a an Koop-Spieler" + SHIFT_LEFT: + name: "&b Umschalttaste Linksklick" + trust: "&a einem Spieler vertrauen" errors: cannot-invite-self: "&c Du kannst dich nicht selbst einladen!" cooldown: "&c Du kannst diese Person für weitere [number] Sekunden nicht @@ -690,12 +792,14 @@ commands: you-joined-island: "&a Du bist einer Insel beigetreten! Benutze /[label] team info um die anderen Mitglieder zu sehen." name-joined-your-island: "&a [name] hat sich deiner Insel angeschlossen!" - confirmation: |- - &c Bist du sicher, dass du diese Einladung annehmen willst? - &c&l Du &n VERLIERST&r&c&l deine jetzige Insel! + confirmation: | + &c Sind Sie sicher, dass Sie + &c diese + &c Einladung annehmen möchten? reject: description: eine Einladung ablehnen - you-rejected-invite: "&a Du hast die Einladung, einer Insel beizutreten, abgelehnt." + you-rejected-invite: "&a Du hast die Einladung, einer Insel beizutreten, + abgelehnt." name-rejected-your-invite: "&c [name] hat deine Insel-Einladung abgelehnt!" cancel: description: die ausstehende Einladung zu deiner Insel zurücknehmen @@ -718,6 +822,7 @@ commands: errors: cant-demote-yourself: "&c Du kannst dich nicht selbst zurückstufen!" cant-demote: "&c Sie können keine höheren Ränge herabstufen!" + must-be-member: "&c Der Spieler muss ein Inselmitglied sein!" failure: "&c Der Spieler kann nicht weiter zurückgestuft werden!" success: "&a Rückstufung von [name] auf [rank]." promote: @@ -726,6 +831,7 @@ commands: errors: cant-promote-yourself: "&c Du kannst dich nicht bewerben!" cant-promote: "&c Sie können nicht über Ihren Rang hinaus aufsteigen!" + must-be-member: "&c Der Spieler muss ein Inselmitglied sein!" failure: "&c Der Spieler kann nicht weiter befördert werden!" success: "&a Beförderung von [name] auf [rank]." setowner: @@ -820,6 +926,10 @@ protection: description: Interaktion umschalten name: Leuchtfeuer hint: Leuchtfeuer-Nutzung deaktiviert + BELL_RINGING: + description: Interaktion umschalten + name: Klingeln zulassen + hint: Klingeln deaktiviert BED: description: Interaktion umschalten name: Betten @@ -866,6 +976,10 @@ protection: description: Umschalten der Knopfnutzung name: Knöpfe hint: Nutzung von Knöpfen deaktiviert + CANDLES: + description: Kerzeninteraktion umschalten + name: Kerzen + hint: Kerzeninteraktion deaktiviert CAKE: description: Kuchen Interaktion umschalten name: Kuchen @@ -1102,6 +1216,11 @@ protection: description: "&a Bienenstockernte umschalten." name: Bienenstockernte hint: Ernte behindert + HURT_TAMED_ANIMALS: + description: Verletzen ein-/ausschalten. Aktiviert bedeutet, dass gezähmte Tiere + Schaden nehmen können. Deaktiviert bedeutet, dass sie unbesiegbar sind. + name: Verletzte gezähmte Tiere + hint: Gezähmtes Tier, das Behinderte verletzt HURT_ANIMALS: description: Umschalten des Verletzens name: Tiere verletzen @@ -1147,6 +1266,7 @@ protection: LEASH: description: Umschalten der Nutzung name: Leine benutzen + hint: Leinengebrauch deaktiviert LECTERN: name: Lesepulte description: "&a Erlaubt es, Bücher auf ein Lesepult zu legen \n&a oder Bücher @@ -1469,14 +1589,6 @@ protection: &7 Aktuelle Einstellung: [setting] setting-active: "&a Aktiv" setting-disabled: "&c Deaktiviert" -language: - panel-title: Wähle deine Sprache - description: - selected: "&a Derzeit ausgewählt." - click-to-select: "&e Klick &a zum Auswählen" - authors: "&a Autoren:" - author: "&3 - &b [name]" - edited: "&a Die Sprache wurde geändert auf &e [lang]&a ." management: panel: title: BentoBox Verwaltung @@ -1586,6 +1698,10 @@ enums: HOT_FLOOR: Heißer Boden CRAMMING: Pauken DRYOUT: Austrocknen + FREEZE: Einfrieren + KILL: Töten + SONIC_BOOM: Überschallknall + WORLD_BORDER: Weltgrenze panel: credits: title: "&8 [name] &2 Credits" @@ -1597,6 +1713,42 @@ panel: description: "&c BentoBox konnte die Mitwirkenden \n&c für dieses Addon nicht erfassen.\n\n&a BentoBox erlauben, sich mit GitHub in \n&a der Konfiguration zu verbinden oder es später erneut versuchen." +panels: + island_creation: + title: "&2&l Wähle eine Insel" + buttons: + bundle: + name: "&l [name]" + description: "[description]" + uses: "&a Verwendet [number]/[max]" + unlimited: "&a Unbegrenzte Nutzung erlaubt" + language: + title: "&2&l Wählen Sie Ihre Sprache" + buttons: + language: + name: "&f&l [name]" + description: |- + [authors] + |[selected] + authors: "&7 Autoren:" + author: "&7 - &b [name]" + selected: "&a Aktuell ausgewählt." + buttons: + previous: + name: "&f&l Vorherige Seite" + description: "&7 Zur Seite [number] wechseln" + next: + name: "&f&l Nächste Seite" + description: "&7 Zur Seite [number] wechseln" + tips: + click-to-next: "&e Klicken Sie auf &7, um weiterzugehen." + click-to-previous: "&e Klicken Sie auf &7, um zum vorherigen zu gelangen." + click-to-choose: "&e Klicken Sie zum Auswählen auf &7." + click-to-toggle: "&e Klicken Sie zum Umschalten auf &7." + left-click-to-cycle-down: "&e Klicken Sie mit der linken Maustaste auf &7, um + nach unten zu blättern." + right-click-to-cycle-up: "&e Klicken Sie mit der rechten Maustaste auf &7, um + nach oben zu blättern." successfully-loaded: |- &6 ____ _ ____ &6 | _ \ | | | _ \ &7 by &a tastybento &7 and &a Poslovitch diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSpawnCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSpawnCommandTest.java index 417d6cc65..3a5e73426 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSpawnCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandSpawnCommandTest.java @@ -127,6 +127,7 @@ public void setUp() throws Exception { when(plugin.getSettings()).thenReturn(s); // IWM + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); when(plugin.getIWM()).thenReturn(iwm); when(iwm.getWorldSettings(any())).thenReturn(ws); map = new HashMap<>(); diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java index 8d712a478..6fb04804c 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java @@ -244,7 +244,7 @@ public void testCanExecuteNoIsland() { */ @Test public void testCanExecuteNoTarget() { - assertTrue(itl.canExecute(user, itl.getLabel(), Collections.emptyList())); + assertFalse(itl.canExecute(user, itl.getLabel(), Collections.emptyList())); // Show panel verify(p).openInventory(any(Inventory.class)); } diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommandTest.java index 7fcd5638b..465d20c58 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamLeaveCommandTest.java @@ -120,6 +120,7 @@ public void setUp() throws Exception { when(Bukkit.getScheduler()).thenReturn(sch); // Island World Manager + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); when(plugin.getIWM()).thenReturn(iwm); // Plugin Manager diff --git a/src/test/java/world/bentobox/bentobox/api/user/UserTest.java b/src/test/java/world/bentobox/bentobox/api/user/UserTest.java index 5a93797dd..ca6775329 100644 --- a/src/test/java/world/bentobox/bentobox/api/user/UserTest.java +++ b/src/test/java/world/bentobox/bentobox/api/user/UserTest.java @@ -119,11 +119,16 @@ public void setUp() throws Exception { // Player when(player.getServer()).thenReturn(server); when(server.getOnlinePlayers()).thenReturn(Collections.emptySet()); + @NonNull + World world = mock(World.class); + when(world.getName()).thenReturn("BSkyBlock"); + when(player.getWorld()).thenReturn(world); // IWM when(plugin.getIWM()).thenReturn(iwm); // Addon - when(iwm .getAddon(any())).thenReturn(Optional.empty()); + when(iwm.getAddon(any())).thenReturn(Optional.empty()); + when(iwm.getFriendlyName(world)).thenReturn("BSkyBlock-Fiendly"); user = User.getInstance(player); @@ -930,7 +935,7 @@ public void testSetAddon() { when(addon.getDescription()).thenReturn(new Builder("main", "gameAddon", "1.0").build()); p.setAddon(addon); p.getTranslation(TEST_TRANSLATION); - verify(addon).getDescription(); + verify(addon, times(3)).getDescription(); } /** diff --git a/src/test/java/world/bentobox/bentobox/listeners/BannedCommandsTest.java b/src/test/java/world/bentobox/bentobox/listeners/BannedCommandsTest.java index 1af8737bc..702ba1739 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/BannedCommandsTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/BannedCommandsTest.java @@ -77,6 +77,7 @@ public void setUp() throws Exception { when(iwm.getPermissionPrefix(any())).thenReturn("bskyblock."); when(iwm.getVisitorBannedCommands(any())).thenReturn(new ArrayList<>()); when(iwm.getFallingBannedCommands(any())).thenReturn(new ArrayList<>()); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); WorldSettings ws = new MyWorldSettings(); when(iwm.getWorldSettings(any())).thenReturn(ws); when(plugin.getIWM()).thenReturn(iwm); diff --git a/src/test/java/world/bentobox/bentobox/listeners/JoinLeaveListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/JoinLeaveListenerTest.java index 43a8b1e1e..431de3936 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/JoinLeaveListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/JoinLeaveListenerTest.java @@ -134,6 +134,7 @@ public void setUp() throws Exception { when(iwm.getAddon(any())).thenReturn(opGm); when(gameMode.getPermissionPrefix()).thenReturn("acidisland."); when(iwm.getIslandDistance(any())).thenReturn(100); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); UUID uuid = UUID.randomUUID(); // Player diff --git a/src/test/java/world/bentobox/bentobox/listeners/StandardSpawnProtectionListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/StandardSpawnProtectionListenerTest.java index ca550760f..b0e48b3f6 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/StandardSpawnProtectionListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/StandardSpawnProtectionListenerTest.java @@ -110,6 +110,7 @@ public void setUp() throws Exception { when(iwm.inWorld(any(World.class))).thenReturn(true); when(iwm.getNetherSpawnRadius(any())).thenReturn(25); when(iwm.getWorldSettings(any())).thenReturn(ws); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); // Util PowerMockito.mockStatic(Util.class); when(Util.getWorld(any())).thenReturn(world); diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java b/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java index d4443627e..8bb864e37 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/AbstractCommonSetup.java @@ -138,6 +138,7 @@ public void setUp() throws Exception { when(plugin.getIWM()).thenReturn(iwm); when(iwm.inWorld(any(Location.class))).thenReturn(true); when(iwm.inWorld(any(World.class))).thenReturn(true); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); // Addon when(iwm.getAddon(any())).thenReturn(Optional.empty()); diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java index a84dd0034..c703dfbfe 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/protection/EntityInteractListenerTest.java @@ -250,10 +250,11 @@ public void testOnPlayerInteractEntityWanderingTraderNoInteraction() { clickedEntity = mock(WanderingTrader.class); when(clickedEntity.getLocation()).thenReturn(location); when(clickedEntity.getType()).thenReturn(EntityType.WANDERING_TRADER); + when(inv.getItemInMainHand()).thenReturn(new ItemStack(Material.STONE)); PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, clickedEntity, hand); eil.onPlayerInteractEntity(e); - verify(notifier, times(2)).notify(any(), eq("protection.protected")); - assertTrue(e.isCancelled()); + verify(notifier, never()).notify(any(), eq("protection.protected")); + assertFalse(e.isCancelled()); } /** @@ -286,8 +287,8 @@ public void testOnPlayerInteractEntityNamingWanderingTraderAllowedNoTrading() { when(clickedEntity.getLocation()).thenReturn(location); PlayerInteractEntityEvent e = new PlayerInteractEntityEvent(mockPlayer, clickedEntity, hand); eil.onPlayerInteractEntity(e); - verify(notifier).notify(any(), eq("protection.protected")); - assertTrue(e.isCancelled()); + verify(notifier, never()).notify(any(), eq("protection.protected")); + assertFalse(e.isCancelled()); } /** diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/settings/PVPListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/settings/PVPListenerTest.java index 10c48be30..146b5b249 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/settings/PVPListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/settings/PVPListenerTest.java @@ -136,6 +136,7 @@ public void setUp() throws Exception { when(iwm.inWorld(any(World.class))).thenReturn(true); when(iwm.inWorld(any(Location.class))).thenReturn(true); when(iwm.getPermissionPrefix(Mockito.any())).thenReturn("bskyblock."); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); // No visitor protection right now when(iwm.getIvSettings(any())).thenReturn(new ArrayList<>()); when(plugin.getIWM()).thenReturn(iwm); diff --git a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListenerTest.java index 8804778fd..e4165e258 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/flags/worldsettings/ObsidianScoopingListenerTest.java @@ -114,6 +114,7 @@ public void setUp() throws Exception { when(iwm.getIslandWorld(Mockito.any())).thenReturn(world); when(iwm.getNetherWorld(Mockito.any())).thenReturn(world); when(iwm.getEndWorld(Mockito.any())).thenReturn(world); + when(iwm.getFriendlyName(any())).thenReturn("BSkyBlock"); // Mock up IslandsManager when(plugin.getIslands()).thenReturn(im); diff --git a/src/test/java/world/bentobox/bentobox/util/ExpiringMapTest.java b/src/test/java/world/bentobox/bentobox/util/ExpiringMapTest.java new file mode 100644 index 000000000..6f86202e0 --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/util/ExpiringMapTest.java @@ -0,0 +1,48 @@ +package world.bentobox.bentobox.util; + +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +public class ExpiringMapTest { + + /** + * Test method for {@link world.bentobox.bentobox.util.ExpiringMap#ExpiringMap(long, java.util.concurrent.TimeUnit)}. + * @throws InterruptedException + */ + @Test + public void testExpiringMap() throws InterruptedException { + ExpiringMap expiringMap = new ExpiringMap<>(5, TimeUnit.SECONDS); + + expiringMap.put("key1", "value1"); + assertEquals(1, expiringMap.size()); + + // Check if key1 is present + assertTrue(expiringMap.containsKey("key1")); + + // Using computeIfAbsent + String value = expiringMap.computeIfAbsent("key2", k -> "computedValue"); + assertEquals("computedValue", value); + assertEquals(2, expiringMap.size()); + + // Check if key2 is present + assertTrue(expiringMap.containsKey("key2")); + + // Use Awaitility to wait for keys to expire + await().atMost(Duration.ofSeconds(6)) + .until(() -> !expiringMap.containsKey("key1") && !expiringMap.containsKey("key2")); + + assertFalse(expiringMap.containsKey("key1")); + assertFalse(expiringMap.containsKey("key2")); + assertTrue(expiringMap.isEmpty()); + + expiringMap.shutdown(); + } + +}