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 extends K, ? extends V> m) {
+ if (m == null) {
+ throw new NullPointerException("The specified map cannot be null.");
+ }
+ for (Entry extends K, ? extends V> 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 super K, ? extends V> 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();
+ }
+
+}