From b73f63a644393c220921cf50f49f8460222a87ed Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 22 Aug 2024 16:53:39 -0700 Subject: [PATCH 01/14] Version 2.5.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a685afaa0..4294b7b9e 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 From 3d3965fe1732683ba9744166167695ce43b2e06f Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 22 Aug 2024 16:54:16 -0700 Subject: [PATCH 02/14] Convert placeholders for [gamemode] and [friendly_name] --- .../world/bentobox/bentobox/api/user/User.java | 16 ++++++++++------ .../bentobox/bentobox/api/user/UserTest.java | 9 +++++++-- 2 files changed, 17 insertions(+), 8 deletions(-) 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..7d0e4f12a 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,16 @@ 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 + translation = translation.replace("[gamemode]", + addon != null ? addon.getDescription().getName() : "[gamemode]"); + if (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/test/java/world/bentobox/bentobox/api/user/UserTest.java b/src/test/java/world/bentobox/bentobox/api/user/UserTest.java index 5a93797dd..1500e5f46 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(2)).getDescription(); } /** From 8d75bc697b02ced0b7f3cfd2e9372357359bfacb Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 22 Aug 2024 19:15:44 -0700 Subject: [PATCH 03/14] Fix tests --- src/main/java/world/bentobox/bentobox/api/user/User.java | 7 ++++--- .../api/commands/island/IslandSpawnCommandTest.java | 1 + .../commands/island/team/IslandTeamLeaveCommandTest.java | 1 + .../java/world/bentobox/bentobox/api/user/UserTest.java | 2 +- .../bentobox/bentobox/listeners/BannedCommandsTest.java | 1 + .../bentobox/bentobox/listeners/JoinLeaveListenerTest.java | 1 + .../listeners/StandardSpawnProtectionListenerTest.java | 1 + .../bentobox/listeners/flags/AbstractCommonSetup.java | 1 + .../bentobox/listeners/flags/settings/PVPListenerTest.java | 1 + .../flags/worldsettings/ObsidianScoopingListenerTest.java | 1 + 10 files changed, 13 insertions(+), 4 deletions(-) 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 7d0e4f12a..8e7c32260 100644 --- a/src/main/java/world/bentobox/bentobox/api/user/User.java +++ b/src/main/java/world/bentobox/bentobox/api/user/User.java @@ -527,9 +527,10 @@ private String replacePrefixes(String translation, String[] variables) { // Replace game mode and friendly name in general // Replace the [gamemode] text variable - translation = translation.replace("[gamemode]", - addon != null ? addon.getDescription().getName() : "[gamemode]"); - if (getWorld() != null) { + 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]"); 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/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 1500e5f46..ca6775329 100644 --- a/src/test/java/world/bentobox/bentobox/api/user/UserTest.java +++ b/src/test/java/world/bentobox/bentobox/api/user/UserTest.java @@ -935,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, times(2)).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/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); From 36fde3cfee27c516a4ec1ca332e91608c4d9ffe7 Mon Sep 17 00:00:00 2001 From: mt-gitlocalize Date: Tue, 27 Aug 2024 20:56:15 +0000 Subject: [PATCH 04/14] Translate de.yml via GitLocalize --- src/main/resources/locales/de.yml | 164 +++++++++++++++++++++++++----- 1 file changed, 137 insertions(+), 27 deletions(-) diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index c0c94254f..7e47f328b 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -138,6 +138,7 @@ 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" success: "&b [name]&a ist jetzt der Besitzer dieser Insel." range: description: Admin Insel Bereichsbefehl @@ -158,7 +159,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 +168,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 +192,7 @@ 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: {} info: parameters: "" description: Informationen über deinen Standort oder die Spielerinsel erhalten @@ -257,10 +259,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 +411,11 @@ 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 resetflags: parameters: "[flag]" description: Alle Inseln in der config.yml auf Standard-Flag-Einstellungen zurücksetzen @@ -498,6 +508,12 @@ 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 + add: {} + remove: {} + 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 +549,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 +619,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 +685,6 @@ 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." uncoop: description: einen Coop-Rang von einem Spieler entfernen parameters: "" @@ -649,8 +701,6 @@ 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." 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 +718,32 @@ 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." 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" + 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 +761,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 +791,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 +800,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 +895,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 +945,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 +1185,10 @@ 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 HURT_ANIMALS: description: Umschalten des Verletzens name: Tiere verletzen @@ -1147,6 +1234,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 +1557,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 +1666,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 +1681,32 @@ 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: + unlimited: "&a Unbegrenzte Nutzung erlaubt" + language: + title: "&2&l Wählen Sie Ihre Sprache" + buttons: + language: + authors: "&7 Autoren:" + selected: "&a Aktuell ausgewählt." + buttons: + previous: + name: "&f&l Vorherige Seite" + next: + name: "&f&l Nächste Seite" + 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 From 8ee366de5116075bb75881a31670d31c8efbef19 Mon Sep 17 00:00:00 2001 From: tastybento Date: Tue, 27 Aug 2024 20:56:16 +0000 Subject: [PATCH 05/14] Translate de.yml via GitLocalize --- src/main/resources/locales/de.yml | 42 +++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index 7e47f328b..2f08fef42 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -139,7 +139,12 @@ commands: 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: @@ -221,6 +226,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." @@ -416,6 +422,7 @@ commands: &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 @@ -511,8 +518,14 @@ commands: completed: "[prefix_bentobox]&a Abgeschlossen" rank: description: Ränge auflisten, hinzufügen oder entfernen - add: {} - remove: {} + 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." @@ -685,6 +698,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 Sie eingeladen, + &a als Genossenschaftsmitglied + &a ihrer Insel beizutreten. uncoop: description: einen Coop-Rang von einem Spieler entfernen parameters: "" @@ -701,6 +718,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 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 ." @@ -718,6 +739,9 @@ 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 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: | @@ -729,6 +753,9 @@ commands: 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: @@ -1189,6 +1216,7 @@ protection: 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 @@ -1686,18 +1714,28 @@ panels: 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." From 7962e3ebc734118b758a0dc1f35599d55a0d4692 Mon Sep 17 00:00:00 2001 From: dawuehr Date: Tue, 27 Aug 2024 20:56:16 +0000 Subject: [PATCH 06/14] Translate de.yml via GitLocalize --- src/main/resources/locales/de.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index 2f08fef42..09f8e3d17 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -197,7 +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: {} + 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 From 5521c8258df5256244f0736c093909da8606f904 Mon Sep 17 00:00:00 2001 From: tastybento Date: Tue, 27 Aug 2024 18:05:50 -0700 Subject: [PATCH 07/14] Add cactus to harvest-able blocks. --- .../listeners/flags/protection/BreakBlocksListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From 86eb9f5eddd5554c33e6c6e7c9f3c8d93f9b5b50 Mon Sep 17 00:00:00 2001 From: bengibbs Date: Wed, 28 Aug 2024 12:54:13 -0700 Subject: [PATCH 08/14] Allow wandering trader interaction at any time #2484 --- .../flags/protection/EntityInteractListener.java | 6 +++++- .../flags/protection/EntityInteractListenerTest.java | 9 +++++---- 2 files changed, 10 insertions(+), 5 deletions(-) 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..65ba74636 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 @@ -42,6 +42,8 @@ public void onPlayerInteractAtEntity(final PlayerInteractAtEntityEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onPlayerInteractEntity(PlayerInteractEntityEvent e) { + System.out.println(e.getEventName()); + System.out.println(e.getRightClicked()); Player p = e.getPlayer(); Location l = e.getRightClicked().getLocation(); @@ -80,8 +82,9 @@ 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)) { + System.out.println("Villager trading"); // Villager trading // Check naming and check trading this.checkIsland(e, p, l, Flags.TRADING); @@ -104,6 +107,7 @@ else if (e.getRightClicked() instanceof Allay) } else if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG)) { + System.out.println("name tag"); // Name tags this.checkIsland(e, p, l, Flags.NAME_TAG); } 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()); } /** From e476f3374399cf50bf23beaaf4b7e5581d775fec Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 29 Aug 2024 17:27:50 -0700 Subject: [PATCH 09/14] Fixes #2486 where spawn island was not being saved --- .../bentobox/bentobox/managers/IslandsManager.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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()); + } } /** From debcd2c3311e661729002e9523939528f794a019 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 31 Aug 2024 08:16:32 -0700 Subject: [PATCH 10/14] Remove debug --- .../listeners/flags/protection/EntityInteractListener.java | 4 ---- 1 file changed, 4 deletions(-) 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 65ba74636..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 @@ -42,8 +42,6 @@ public void onPlayerInteractAtEntity(final PlayerInteractAtEntityEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onPlayerInteractEntity(PlayerInteractEntityEvent e) { - System.out.println(e.getEventName()); - System.out.println(e.getRightClicked()); Player p = e.getPlayer(); Location l = e.getRightClicked().getLocation(); @@ -84,7 +82,6 @@ else if (e.getRightClicked() instanceof Boat) } else if (e.getRightClicked() instanceof Villager && !(e.getRightClicked() instanceof WanderingTrader)) { - System.out.println("Villager trading"); // Villager trading // Check naming and check trading this.checkIsland(e, p, l, Flags.TRADING); @@ -107,7 +104,6 @@ else if (e.getRightClicked() instanceof Allay) } else if (e.getPlayer().getInventory().getItemInMainHand().getType().equals(Material.NAME_TAG)) { - System.out.println("name tag"); // Name tags this.checkIsland(e, p, l, Flags.NAME_TAG); } From 05524ae69c57f739a347446715b231618dcb1c99 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 31 Aug 2024 08:20:39 -0700 Subject: [PATCH 11/14] Clean up prior invites #2488 --- .../island/team/IslandTeamCommand.java | 19 ++++++++++++++++--- .../team/IslandTeamInviteAcceptCommand.java | 2 ++ .../island/team/IslandTeamInviteCommand.java | 5 ++++- .../team/IslandTeamInviteCommandTest.java | 2 +- 4 files changed, 23 insertions(+), 5 deletions(-) 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/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)); } From 2a56414b40fbfba89d677e78697d065f815b76e6 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 31 Aug 2024 09:06:54 -0700 Subject: [PATCH 12/14] WIP --- .../world/bentobox/bentobox/BentoBox.java | 2 + .../bentobox/util/heads/ExpiringMap.java | 74 +++++++++++++++++++ .../bentobox/util/heads/HeadGetter.java | 16 +++- 3 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/util/heads/ExpiringMap.java 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/util/heads/ExpiringMap.java b/src/main/java/world/bentobox/bentobox/util/heads/ExpiringMap.java new file mode 100644 index 000000000..95dcb3c1c --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/util/heads/ExpiringMap.java @@ -0,0 +1,74 @@ +package world.bentobox.bentobox.util.heads; + +import java.util.Map; +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; + +public class ExpiringMap { + private final Map map; + private final ScheduledExecutorService scheduler; + private final long expirationTime; + + public ExpiringMap(long expirationTime, TimeUnit timeUnit) { + this.map = new ConcurrentHashMap<>(); + this.scheduler = Executors.newSingleThreadScheduledExecutor(); + this.expirationTime = timeUnit.toMillis(expirationTime); + } + + public void put(K key, V value) { + map.put(key, value); + scheduleRemoval(key); + } + + public boolean containsKey(K key) { + return map.containsKey(key); + } + + public V get(K key) { + return map.get(key); + } + + public V remove(K key) { + return map.remove(key); + } + + public int size() { + return map.size(); + } + + public V computeIfAbsent(K key, Function mappingFunction) { + return map.computeIfAbsent(key, k -> { + V value = mappingFunction.apply(k); + scheduleRemoval(k); + return value; + }); + } + + private void scheduleRemoval(final K key) { + scheduler.schedule(() -> map.remove(key), expirationTime, TimeUnit.MILLISECONDS); + } + + public void shutdown() { + scheduler.shutdown(); + } + /* + public static void main(String[] args) throws InterruptedException { + ExpiringMap expiringMap = new ExpiringMap<>(5, TimeUnit.SECONDS); + + expiringMap.put("key1", "value1"); + System.out.println("Initial size: " + expiringMap.size()); // Should print 1 + + // Using computeIfAbsent + String value = expiringMap.computeIfAbsent("key2", k -> "computedValue"); + System.out.println("Computed value for key2: " + value); // Should print "computedValue" + System.out.println("Size after computeIfAbsent: " + expiringMap.size()); // Should print 2 + + Thread.sleep(6000); + System.out.println("Size after 6 seconds: " + expiringMap.size()); // Should print 0 + + expiringMap.shutdown(); + }*/ +} 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..b2c9c716c 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; @@ -36,7 +35,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 +45,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 +65,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 From caae0af50e675f7a36ef13f792a812f38643aa91 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 31 Aug 2024 09:50:11 -0700 Subject: [PATCH 13/14] Added cache expiration for head getting. Heads must be gotten within 5 seconds otherwise the cache is cleared. Heads that have been retrieved will be kept in cache for 1 day --- pom.xml | 7 + .../bentobox/bentobox/util/ExpiringMap.java | 271 ++++++++++++++++++ .../bentobox/util/heads/ExpiringMap.java | 74 ----- .../bentobox/util/heads/HeadGetter.java | 1 + .../bentobox/util/ExpiringMapTest.java | 48 ++++ 5 files changed, 327 insertions(+), 74 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/util/ExpiringMap.java delete mode 100644 src/main/java/world/bentobox/bentobox/util/heads/ExpiringMap.java create mode 100644 src/test/java/world/bentobox/bentobox/util/ExpiringMapTest.java diff --git a/pom.xml b/pom.xml index 4294b7b9e..c4b5ccd78 100644 --- a/pom.xml +++ b/pom.xml @@ -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/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/ExpiringMap.java b/src/main/java/world/bentobox/bentobox/util/heads/ExpiringMap.java deleted file mode 100644 index 95dcb3c1c..000000000 --- a/src/main/java/world/bentobox/bentobox/util/heads/ExpiringMap.java +++ /dev/null @@ -1,74 +0,0 @@ -package world.bentobox.bentobox.util.heads; - -import java.util.Map; -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; - -public class ExpiringMap { - private final Map map; - private final ScheduledExecutorService scheduler; - private final long expirationTime; - - public ExpiringMap(long expirationTime, TimeUnit timeUnit) { - this.map = new ConcurrentHashMap<>(); - this.scheduler = Executors.newSingleThreadScheduledExecutor(); - this.expirationTime = timeUnit.toMillis(expirationTime); - } - - public void put(K key, V value) { - map.put(key, value); - scheduleRemoval(key); - } - - public boolean containsKey(K key) { - return map.containsKey(key); - } - - public V get(K key) { - return map.get(key); - } - - public V remove(K key) { - return map.remove(key); - } - - public int size() { - return map.size(); - } - - public V computeIfAbsent(K key, Function mappingFunction) { - return map.computeIfAbsent(key, k -> { - V value = mappingFunction.apply(k); - scheduleRemoval(k); - return value; - }); - } - - private void scheduleRemoval(final K key) { - scheduler.schedule(() -> map.remove(key), expirationTime, TimeUnit.MILLISECONDS); - } - - public void shutdown() { - scheduler.shutdown(); - } - /* - public static void main(String[] args) throws InterruptedException { - ExpiringMap expiringMap = new ExpiringMap<>(5, TimeUnit.SECONDS); - - expiringMap.put("key1", "value1"); - System.out.println("Initial size: " + expiringMap.size()); // Should print 1 - - // Using computeIfAbsent - String value = expiringMap.computeIfAbsent("key2", k -> "computedValue"); - System.out.println("Computed value for key2: " + value); // Should print "computedValue" - System.out.println("Size after computeIfAbsent: " + expiringMap.size()); // Should print 2 - - Thread.sleep(6000); - System.out.println("Size after 6 seconds: " + expiringMap.size()); // Should print 0 - - expiringMap.shutdown(); - }*/ -} 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 b2c9c716c..813a5ce1f 100644 --- a/src/main/java/world/bentobox/bentobox/util/heads/HeadGetter.java +++ b/src/main/java/world/bentobox/bentobox/util/heads/HeadGetter.java @@ -24,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; /** 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(); + } + +} From 3fbfd27e64de904fafcdbf756c4e5db6e8e269bf Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 31 Aug 2024 16:28:09 -0700 Subject: [PATCH 14/14] Use a temporary cache for players of 2 hours --- .../world/bentobox/bentobox/managers/PlayersManager.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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(); } /**