diff --git a/build.gradle.kts b/build.gradle.kts index 2fe7b6a75..71f0fcc51 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,7 +46,7 @@ paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArt group = "world.bentobox" // From // Base properties from -val buildVersion = "3.17.0" +val buildVersion = "3.17.1" val buildNumberDefault = "-LOCAL" // Local build identifier val snapshotSuffix = "-SNAPSHOT" // Indicates development/snapshot version diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeCommand.java index 05b4afa39..28c1176d0 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeCommand.java @@ -24,6 +24,8 @@ public void setup() { new AdminRangeResetCommand(this); new AdminRangeAddCommand(this); new AdminRangeRemoveCommand(this); + new AdminRangeRemoveBonusCommand(this); + new AdminRangePurgeBonusCommand(this); } @Override diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangePurgeBonusCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangePurgeBonusCommand.java new file mode 100644 index 000000000..7a2b7582a --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangePurgeBonusCommand.java @@ -0,0 +1,184 @@ +package world.bentobox.bentobox.api.commands.admin.range; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.bukkit.Bukkit; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.events.island.IslandEvent; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.BonusRangeRecord; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; + +/** + * Admin command to remove every bonus range carrying a given id from all + * islands in this gamemode's world. + *

+ * Addons tag the bonus ranges they grant with their own name (the bonus + * {@code uniqueId}); for example the Upgrades addon stores them under the id + * {@code "Upgrades"}. When such an addon is removed, the bonus ranges it added + * remain on every island it ever touched. This command purges them in one go. + *

+ * Because a server can hold a large number of islands, the scan that finds the + * affected islands runs asynchronously (off the main thread). The admin is then + * shown the count and must re-run the command with {@code confirm} to apply it. + * The actual mutation and event firing happen back on the main thread, on the + * live cached island instances. + * + * @author tastybento + * @since 3.17.1 + */ +public class AdminRangePurgeBonusCommand extends CompositeCommand { + + /** True while an async scan is running, to prevent overlapping runs. */ + volatile boolean inPurge; + /** True once a scan has found islands and is awaiting a {@code confirm}. */ + boolean toBeConfirmed; + /** The bonus id the pending confirmation is for. */ + @Nullable + String pendingId; + /** The unique ids of the islands the pending confirmation will purge. */ + List pendingIslandIds = List.of(); + + /** + * Admin command to remove a bonus range id from every island. + * @param parent - parent range command + */ + public AdminRangePurgeBonusCommand(CompositeCommand parent) { + super(parent, "purgebonus"); + } + + @Override + public void setup() { + setPermission("admin.range.purgebonus"); + setOnlyPlayer(false); + setParametersHelp("commands.admin.range.purgebonus.parameters"); + setDescription("commands.admin.range.purgebonus.description"); + } + + @Override + public boolean canExecute(User user, String label, List args) { + if (inPurge) { + user.sendMessage("commands.admin.range.purgebonus.in-progress"); + return false; + } + // Expect "" or " confirm" + if (args.isEmpty() || args.size() > 2 + || (args.size() == 2 && !args.get(1).equalsIgnoreCase("confirm"))) { + showHelp(this, user); + return false; + } + return true; + } + + @Override + public boolean execute(User user, String label, List args) { + String id = args.get(0); + boolean confirm = args.size() == 2 && args.get(1).equalsIgnoreCase("confirm"); + // Apply a pending purge if this is the matching confirmation + if (confirm && toBeConfirmed && id.equals(pendingId)) { + List ids = pendingIslandIds; + toBeConfirmed = false; + pendingId = null; + pendingIslandIds = List.of(); + applyPurge(user, id, ids); + return true; + } + // Otherwise (re)scan asynchronously and prompt for confirmation + inPurge = true; + getPlugin().getIslands().getIslandsASync().thenAccept(all -> { + List ids = findIslandIds(all, id); + Bukkit.getScheduler().runTask(getPlugin(), () -> { + inPurge = false; + if (ids.isEmpty()) { + user.sendMessage("commands.admin.range.purgebonus.none", "[id]", id); + return; + } + pendingId = id; + pendingIslandIds = ids; + toBeConfirmed = true; + user.sendMessage("commands.admin.range.purgebonus.warning", "[id]", id, TextVariables.NUMBER, + String.valueOf(ids.size())); + user.sendMessage("commands.admin.range.purgebonus.confirm"); + }); + }).exceptionally(ex -> { + getPlugin().logStacktrace(ex); + Bukkit.getScheduler().runTask(getPlugin(), () -> { + inPurge = false; + user.sendMessage("commands.admin.range.purgebonus.failed"); + }); + return null; + }); + return true; + } + + /** + * @return the unique ids of the islands in this world that carry a bonus range + * with the given id. + */ + List findIslandIds(Collection islands, String id) { + return islands.stream().filter(i -> getWorld().equals(i.getWorld())) + .filter(i -> i.getBonusRangeRecord(id).isPresent()).map(Island::getUniqueId).toList(); + } + + /** + * Removes the bonus range id from every still-matching island, firing a range + * change event per island whose effective protection range changed. Runs on the + * main thread and operates on the live cached island instances, which are + * persisted automatically via {@code setChanged()}. + * + * @param user the admin running the command + * @param id the bonus range uniqueId to purge + * @param ids the unique ids of the islands found during the async scan + */ + void applyPurge(User user, String id, List ids) { + int changed = 0; + for (String uid : ids) { + Island island = getIslands().getIslandById(uid).orElse(null); + // Re-check on the live instance in case it changed since the scan + if (island == null || island.getBonusRangeRecord(id).isEmpty()) { + continue; + } + int oldRange = island.getProtectionRange(); + island.clearBonusRange(id); + int newRange = island.getProtectionRange(); + if (oldRange != newRange) { + IslandEvent.builder() + .island(island) + .location(island.getCenter()) + .reason(IslandEvent.Reason.RANGE_CHANGE) + .involvedPlayer(island.getOwner()) + .admin(true) + .protectionRange(newRange, oldRange) + .build(); + } + changed++; + } + getPlugin().log("Purged bonus range '" + id + "' from " + changed + " island(s) in " + + getWorld().getName()); + user.sendMessage("commands.admin.range.purgebonus.success", "[id]", id, TextVariables.NUMBER, + String.valueOf(changed)); + } + + @Override + public Optional> tabComplete(User user, String alias, List args) { + if (args.size() <= 1) { + String lastArg = !args.isEmpty() ? args.getLast() : ""; + // Suggest the bonus ids that exist on currently-loaded islands in this world + Collection cached = getIslands().getIslandCache().getCachedIslands(); + List ids = cached.stream().filter(i -> getWorld().equals(i.getWorld())) + .flatMap(i -> i.getBonusRanges().stream()).map(BonusRangeRecord::getUniqueId).distinct().sorted() + .toList(); + return Optional.of(Util.tabLimit(new ArrayList<>(ids), lastArg)); + } else if (args.size() == 2) { + return Optional.of(Util.tabLimit(new ArrayList<>(List.of("confirm")), args.getLast())); + } + return Optional.empty(); + } +} diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeRemoveBonusCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeRemoveBonusCommand.java new file mode 100644 index 000000000..928f7a2e1 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeRemoveBonusCommand.java @@ -0,0 +1,164 @@ +package world.bentobox.bentobox.api.commands.admin.range; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.commands.ConfirmableCommand; +import world.bentobox.bentobox.api.events.island.IslandEvent; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.BonusRangeRecord; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; + +/** + * Admin command to remove bonus ranges from a player's island. + *

+ * Bonus ranges are static values added to (or subtracted from) the protection + * range of an island. They are stored in the {@link Island} as + * {@link BonusRangeRecord}s and are typically granted by addons. Admins need a + * way to clear them - for example after the addon that granted them has been + * removed. + *

+ * Usage: {@code / range removebonus [id]}. With no id, every + * bonus range is removed. With an id, only the bonus ranges sharing that unique + * id are removed. The available ids are offered through tab completion so the + * admin does not have to guess them. + * + * @author tastybento + * @since 3.17.1 + */ +public class AdminRangeRemoveBonusCommand extends ConfirmableCommand { + + private Island targetIsland; + private @Nullable UUID targetUUID; + private @Nullable String bonusId; + + /** + * Admin command to remove bonus ranges from a player's island. + * @param parent - parent range command + */ + public AdminRangeRemoveBonusCommand(CompositeCommand parent) { + super(parent, "removebonus"); + } + + @Override + public void setup() { + setPermission("admin.range.removebonus"); + setOnlyPlayer(false); + setParametersHelp("commands.admin.range.removebonus.parameters"); + setDescription("commands.admin.range.removebonus.description"); + } + + @Override + public boolean canExecute(User user, String label, List args) { + // Expect the player's name and an optional bonus id + if (args.isEmpty() || args.size() > 2) { + showHelp(this, user); + return false; + } + // Get target player + targetUUID = Util.getUUID(args.get(0)); + if (targetUUID == null) { + user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); + return false; + } + // Target must have an island in this world + targetIsland = getIslands().getIsland(getWorld(), targetUUID); + if (targetIsland == null) { + user.sendMessage("general.errors.player-has-no-island"); + return false; + } + // Nothing to do if there are no bonus ranges at all + if (targetIsland.getBonusRanges().isEmpty()) { + user.sendMessage("commands.admin.range.removebonus.no-bonus"); + return false; + } + // A specific bonus id was supplied - it must exist on this island + bonusId = args.size() == 2 ? args.get(1) : null; + if (bonusId != null && targetIsland.getBonusRangeRecord(bonusId).isEmpty()) { + user.sendMessage("commands.admin.range.removebonus.unknown-bonus", "[id]", bonusId); + return false; + } + return true; + } + + @Override + public boolean execute(User user, String label, List args) { + Objects.requireNonNull(targetIsland); + Objects.requireNonNull(targetUUID); + askConfirmation(user, () -> removeBonusRanges(user, args.get(0))); + return true; + } + + /** + * Removes the bonus range(s) from the target island. If {@link #bonusId} is + * set, only that id's bonus ranges are removed, otherwise all of them are. + * Fires a range change event if the effective protection range changed and + * notifies the user. The island is persisted automatically via {@code setChanged()}. + * + * @param user the admin running the command + * @param name the target player's name (for the feedback message) + */ + void removeBonusRanges(User user, String name) { + int oldRange = targetIsland.getProtectionRange(); + + int removed; + if (bonusId == null) { + // Remove every bonus range + removed = targetIsland.getBonusRanges().stream().mapToInt(BonusRangeRecord::getRange).sum(); + targetIsland.clearAllBonusRanges(); + user.sendMessage("commands.admin.range.removebonus.success", TextVariables.NUMBER, + String.valueOf(removed), TextVariables.NAME, name); + } else { + // Remove only the bonus ranges for the given id + removed = targetIsland.getBonusRange(bonusId); + targetIsland.clearBonusRange(bonusId); + user.sendMessage("commands.admin.range.removebonus.success-id", "[id]", bonusId, + TextVariables.NUMBER, String.valueOf(removed), TextVariables.NAME, name); + } + + // The effective protection range may have changed - notify addons (not cancellable) + int newRange = targetIsland.getProtectionRange(); + if (oldRange != newRange) { + IslandEvent.builder() + .island(targetIsland) + .location(targetIsland.getCenter()) + .reason(IslandEvent.Reason.RANGE_CHANGE) + .involvedPlayer(targetUUID) + .admin(true) + .protectionRange(newRange, oldRange) + .build(); + } + } + + @Override + public Optional> tabComplete(User user, String alias, List args) { + String lastArg = !args.isEmpty() ? args.getLast() : ""; + if (args.size() <= 1) { + // Don't show every player on the server. Require at least the first letter + if (lastArg.isEmpty()) { + return Optional.empty(); + } + return Optional.of(Util.tabLimit(new ArrayList<>(Util.getOnlinePlayerList(user)), lastArg)); + } else if (args.size() == 2) { + // Offer the bonus ids that exist on the target player's island + UUID uuid = Util.getUUID(args.get(0)); + if (uuid != null) { + Island island = getIslands().getIsland(getWorld(), uuid); + if (island != null) { + List ids = island.getBonusRanges().stream().map(BonusRangeRecord::getUniqueId).distinct() + .toList(); + return Optional.of(Util.tabLimit(new ArrayList<>(ids), lastArg)); + } + } + } + return Optional.empty(); + } +} diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml index e0300eafd..b0e3b35eb 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -241,6 +241,22 @@ commands: success: >- Úspěšně zmenšena chráněná oblast ostrova hráče [name]na [total] (-[number]). + removebonus: + parameters: ' [id]' + description: 'odebere bonusové rozsahy z ostrova (všechny nebo jeden podle id)' + no-bonus: 'Tento ostrov nemá žádné bonusové rozsahy k odebrání!' + unknown-bonus: 'Na tomto ostrově neexistuje bonusový rozsah s id [id]!' + success: 'Odebráno [number] z bonusového rozsahu ostrova hráče [name].' + success-id: 'Odebrán bonusový rozsah [id] ([number]) z ostrova hráče [name].' + purgebonus: + parameters: '' + description: 'odebere bonusový rozsah s daným id ze všech ostrovů (např. po odebrání addonu, který jej přidal)' + none: 'Žádný ostrov nemá bonusový rozsah s id [id]!' + warning: 'Tímto odeberete bonusový rozsah [id] z [number] ostrovů.' + success: 'Odebrán bonusový rozsah [id] z [number] ostrovů.' + confirm: 'Spusťte příkaz znovu s confirm pro potvrzení.' + in-progress: 'Čištění bonusových rozsahů již probíhá. Počkejte prosím.' + failed: 'Skenování ostrovů selhalo. Zkontrolujte konzoli.' register: parameters: description: registrovat hráče na nevlastněný ostrov, na kterém se nacházíš diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index f8c6a1c5f..4d937ab31 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -261,6 +261,22 @@ commands: success: >- Erfolgreich reduziert [name]'s geschützten Bereich der Insel um[total] (-[number]). + removebonus: + parameters: ' [id]' + description: 'entfernt Bonusbereiche von der Insel (alle oder einen per id)' + no-bonus: 'Diese Insel hat keine Bonusbereiche zum Entfernen!' + unknown-bonus: 'Es gibt keinen Bonusbereich mit der id [id] auf dieser Insel!' + success: 'Entfernt: [number] an Bonusbereich von der Insel von [name].' + success-id: 'Bonusbereich [id] ([number]) von der Insel von [name] entfernt.' + purgebonus: + parameters: '' + description: 'entfernt einen Bonusbereich mit dieser id von allen Inseln (z. B. nach dem Entfernen des Addons, das ihn hinzugefügt hat)' + none: 'Keine Insel hat einen Bonusbereich mit der id [id]!' + warning: 'Dies entfernt den Bonusbereich [id] von [number] Insel(n).' + success: 'Bonusbereich [id] von [number] Insel(n) entfernt.' + confirm: 'Führe den Befehl erneut mit confirm aus, um fortzufahren.' + in-progress: 'Eine Bonusbereich-Bereinigung läuft bereits. Bitte warten.' + failed: 'Inseln konnten nicht gescannt werden. Prüfe die Konsole.' register: parameters: description: Spieler auf freier Insel registrieren, auf der du dich befindest diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 47a15afce..c5cd0fa87 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -221,6 +221,22 @@ commands: parameters: [[prefix_island] location] success: 'Successfully decreased [name]''s [prefix_island] protected range to [total] (-[number]).' + removebonus: + parameters: [id] + description: removes bonus ranges from the [prefix_island] (all, or one by id) + no-bonus: 'This [prefix_island] has no bonus ranges to remove!' + unknown-bonus: 'There is no bonus range with id [id] on this [prefix_island]!' + success: 'Removed [number] of bonus range from [name]''s [prefix_island].' + success-id: 'Removed bonus range [id] ([number]) from [name]''s [prefix_island].' + purgebonus: + parameters: + description: removes a bonus range id from every [prefix_island] (e.g. after removing the addon that added it) + none: 'No [prefix_island] has a bonus range with id [id]!' + warning: 'This will remove bonus range [id] from [number] island(s).' + success: 'Removed bonus range [id] from [number] island(s).' + confirm: 'Run the command again with confirm to proceed.' + in-progress: 'A bonus range purge is already running. Please wait.' + failed: 'Failed to scan islands. Check the console.' register: parameters: description: register player to unowned [prefix_island] you are on diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index fc1ad2e18..ccc930bf3 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -247,6 +247,22 @@ commands: success: >- Disminuyó exitosamente [name] rango protegido de la isla a [total] (- [number] ) . + removebonus: + parameters: ' [id]' + description: 'elimina rangos de bonificación de la isla (todos, o uno por id)' + no-bonus: '¡Esta isla no tiene rangos de bonificación que eliminar!' + unknown-bonus: '¡No existe ningún rango de bonificación con la id [id] en esta isla!' + success: 'Se eliminaron [number] de rango de bonificación de la isla de [name].' + success-id: 'Se eliminó el rango de bonificación [id] ([number]) de la isla de [name].' + purgebonus: + parameters: '' + description: 'elimina un rango de bonificación con esa id de todas las islas (p. ej. tras eliminar el addon que lo añadió)' + none: '¡Ninguna isla tiene un rango de bonificación con la id [id]!' + warning: 'Esto eliminará el rango de bonificación [id] de [number] isla(s).' + success: 'Rango de bonificación [id] eliminado de [number] isla(s).' + confirm: 'Vuelve a ejecutar el comando con confirm para continuar.' + in-progress: 'Ya hay una purga de rangos de bonificación en curso. Espera.' + failed: 'No se pudieron escanear las islas. Revisa la consola.' register: parameters: description: registrar jugador en la isla sin dueño en la que estás diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index 30d18e00d..8addcdcb5 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -265,6 +265,22 @@ commands: success: >- Diminution réussie de la zone de protection de l'île de [name] à [total] (-[number]). + removebonus: + parameters: ' [id]' + description: 'supprime les rayons bonus de l''île (tous, ou un seul par id)' + no-bonus: 'Cette île n''a aucun rayon bonus à supprimer !' + unknown-bonus: 'Il n''existe aucun rayon bonus avec l''id [id] sur cette île !' + success: 'Suppression de [number] de rayon bonus de l''île de [name].' + success-id: 'Rayon bonus [id] ([number]) supprimé de l''île de [name].' + purgebonus: + parameters: '' + description: 'supprime un rayon bonus avec cet id de toutes les îles (par ex. après la suppression de l''addon qui l''a ajouté)' + none: 'Aucune île n''a de rayon bonus avec l''id [id]!' + warning: 'Ceci supprimera le rayon bonus [id] de [number] île(s).' + success: 'Rayon bonus [id] supprimé de [number] île(s).' + confirm: 'Relancez la commande avec confirm pour continuer.' + in-progress: 'Une purge des rayons bonus est déjà en cours. Veuillez patienter.' + failed: 'Échec de l''analyse des îles. Consultez la console.' register: parameters: description: enregistrer le joueur sur une île sans propriétaire où vous êtes diff --git a/src/main/resources/locales/hr.yml b/src/main/resources/locales/hr.yml index 48a499e89..bc6aa9ce3 100644 --- a/src/main/resources/locales/hr.yml +++ b/src/main/resources/locales/hr.yml @@ -249,6 +249,22 @@ commands: success: >- Uspješno je smanjio [name]zaštićeni domet otoka na [total] (-[number]). + removebonus: + parameters: ' [id]' + description: 'uklanja bonus raspone s otoka (sve ili jedan po id-u)' + no-bonus: 'Ovaj otok nema bonus raspone za uklanjanje!' + unknown-bonus: 'Ne postoji bonus raspon s id-om [id] na ovom otoku!' + success: 'Uklonjeno [number] bonus raspona s otoka igrača [name].' + success-id: 'Uklonjen bonus raspon [id] ([number]) s otoka igrača [name].' + purgebonus: + parameters: '' + description: 'uklanja bonus raspon s danim id-om sa svih otoka (npr. nakon uklanjanja dodatka koji ga je dodao)' + none: 'Nijedan otok nema bonus raspon s id-om [id]!' + warning: 'Ovo će ukloniti bonus raspon [id] s [number] otoka.' + success: 'Uklonjen bonus raspon [id] s [number] otoka.' + confirm: 'Ponovno pokrenite naredbu s confirm za potvrdu.' + in-progress: 'Čišćenje bonus raspona već je u tijeku. Pričekajte.' + failed: 'Skeniranje otoka nije uspjelo. Provjerite konzolu.' register: parameters: description: registrirajte igrača na nevlasnički otok na kojem se nalazite diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index e6e697538..f65e07df5 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -263,6 +263,22 @@ commands: success: >- Sikeresen csökkentette [name][prefix_island] védett tartományát [total] -re (-[number]). + removebonus: + parameters: ' [id]' + description: 'eltávolítja a bónusz hatótávolságokat a szigetről (mindet, vagy egyet id alapján)' + no-bonus: 'Ezen a szigeten nincs eltávolítható bónusz hatótávolság!' + unknown-bonus: 'Nincs ilyen id-jű bónusz hatótávolság [id] ezen a szigeten!' + success: 'Eltávolítva [number] bónusz hatótávolság [name] szigetéről.' + success-id: 'Eltávolítva a(z) [id] bónusz hatótávolság ([number]) [name] szigetéről.' + purgebonus: + parameters: '' + description: 'eltávolít egy adott id-jű bónusz hatótávolságot az összes szigetről (pl. az azt hozzáadó kiegészítő eltávolítása után)' + none: 'Egyetlen szigetnek sincs [id] id-jű bónusz hatótávolsága!' + warning: 'Ez eltávolítja a(z) [id] bónusz hatótávolságot [number] szigetről.' + success: 'A(z) [id] bónusz hatótávolság eltávolítva [number] szigetről.' + confirm: 'Futtasd újra a parancsot a confirm kapcsolóval a megerősítéshez.' + in-progress: 'Egy bónusz hatótávolság tisztítás már fut. Kérlek várj.' + failed: 'A szigetek beolvasása sikertelen. Ellenőrizd a konzolt.' register: parameters: description: regisztrálj játékost egy ismeretlen szigetre, amelyen tartózkodsz diff --git a/src/main/resources/locales/id.yml b/src/main/resources/locales/id.yml index 9d6808b49..58099784e 100644 --- a/src/main/resources/locales/id.yml +++ b/src/main/resources/locales/id.yml @@ -250,6 +250,22 @@ commands: success: >- Berhasil menurunkan jangkauan perlindungan pulau [name] menjadi [total] (-[number]). + removebonus: + parameters: ' [id]' + description: 'menghapus rentang bonus dari pulau (semua, atau satu berdasarkan id)' + no-bonus: 'Pulau ini tidak memiliki rentang bonus untuk dihapus!' + unknown-bonus: 'Tidak ada rentang bonus dengan id [id] di pulau ini!' + success: 'Menghapus [number] rentang bonus dari pulau [name].' + success-id: 'Menghapus rentang bonus [id] ([number]) dari pulau [name].' + purgebonus: + parameters: '' + description: 'menghapus rentang bonus dengan id tersebut dari semua pulau (mis. setelah menghapus addon yang menambahkannya)' + none: 'Tidak ada pulau yang memiliki rentang bonus dengan id [id]!' + warning: 'Ini akan menghapus rentang bonus [id] dari [number] pulau.' + success: 'Rentang bonus [id] dihapus dari [number] pulau.' + confirm: 'Jalankan lagi perintah dengan confirm untuk melanjutkan.' + in-progress: 'Pembersihan rentang bonus sedang berjalan. Mohon tunggu.' + failed: 'Gagal memindai pulau. Periksa konsol.' register: parameters: description: daftarkan pemain ke pulau tak berpemilik tempat Anda berada diff --git a/src/main/resources/locales/it.yml b/src/main/resources/locales/it.yml index 155a7ba83..a5123b237 100644 --- a/src/main/resources/locales/it.yml +++ b/src/main/resources/locales/it.yml @@ -256,6 +256,22 @@ commands: success: >- Diminuito il raggio di protezione della isola di [name]al raggio di [total] (-[number]). + removebonus: + parameters: ' [id]' + description: 'rimuove i raggi bonus dall''isola (tutti o uno per id)' + no-bonus: 'Quest''isola non ha raggi bonus da rimuovere!' + unknown-bonus: 'Non esiste alcun raggio bonus con id [id] su quest''isola!' + success: 'Rimossi [number] di raggio bonus dall''isola di [name].' + success-id: 'Raggio bonus [id] ([number]) rimosso dall''isola di [name].' + purgebonus: + parameters: '' + description: 'rimuove un raggio bonus con quell''id da tutte le isole (ad es. dopo aver rimosso l''addon che lo ha aggiunto)' + none: 'Nessuna isola ha un raggio bonus con id [id]!' + warning: 'Questo rimuoverà il raggio bonus [id] da [number] isola/e.' + success: 'Raggio bonus [id] rimosso da [number] isola/e.' + confirm: 'Esegui di nuovo il comando con confirm per procedere.' + in-progress: 'È già in corso una rimozione dei raggi bonus. Attendi.' + failed: 'Scansione delle isole non riuscita. Controlla la console.' register: parameters: description: registra il giocatore nell'isola senza proprietario sulla quale sei diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml index 1abc5b0ca..246fd7f04 100644 --- a/src/main/resources/locales/ja.yml +++ b/src/main/resources/locales/ja.yml @@ -215,6 +215,22 @@ commands: description: 島の保護範囲を縮小します parameters: <プレーヤー> <範囲> success: '[name]の島の保護範囲を[total](-[number])に減らすことに成功しました。' + removebonus: + parameters: ' [id]' + description: '島からボーナス範囲を削除します(すべて、またはidで1つ)' + no-bonus: 'この島には削除できるボーナス範囲がありません!' + unknown-bonus: 'この島にid [id] のボーナス範囲はありません!' + success: '[name]の島からボーナス範囲 [number] を削除しました。' + success-id: '[name]の島からボーナス範囲 [id][number])を削除しました。' + purgebonus: + parameters: '' + description: '指定したidのボーナス範囲をすべての島から削除します(例:追加したアドオンを削除した後)' + none: 'id [id] のボーナス範囲を持つ島はありません!' + warning: 'ボーナス範囲 [id][number] 個の島から削除します。' + success: 'ボーナス範囲 [id][number] 個の島から削除しました。' + confirm: '続行するには、コマンドに confirm を付けて再実行してください。' + in-progress: 'ボーナス範囲の削除がすでに実行中です。お待ちください。' + failed: '島のスキャンに失敗しました。コンソールを確認してください。' register: parameters: <プレーヤー> description: あなたがいる未所有の島にプレーヤーを登録する。 diff --git a/src/main/resources/locales/ko.yml b/src/main/resources/locales/ko.yml index d69a9ca61..cf0721228 100644 --- a/src/main/resources/locales/ko.yml +++ b/src/main/resources/locales/ko.yml @@ -232,6 +232,22 @@ commands: success: >- 성공적으로 [name]의 섬의 보호범위를 줄였습니다 토탈 : [total] (-[number] ). + removebonus: + parameters: ' [id]' + description: '섬에서 보너스 범위를 제거합니다 (전체 또는 id로 하나)' + no-bonus: '이 섬에는 제거할 보너스 범위가 없습니다!' + unknown-bonus: '이 섬에 id [id] 인 보너스 범위가 없습니다!' + success: '[name] 님의 섬에서 보너스 범위 [number] 을(를) 제거했습니다.' + success-id: '[name] 님의 섬에서 보너스 범위 [id] ([number]) 을(를) 제거했습니다.' + purgebonus: + parameters: '' + description: '지정한 id의 보너스 범위를 모든 섬에서 제거합니다 (예: 추가한 애드온을 제거한 후)' + none: 'id [id] 인 보너스 범위를 가진 섬이 없습니다!' + warning: '보너스 범위 [id] 을(를) [number] 개의 섬에서 제거합니다.' + success: '보너스 범위 [id] 을(를) [number] 개의 섬에서 제거했습니다.' + confirm: '계속하려면 confirm 을(를) 붙여 명령어를 다시 실행하세요.' + in-progress: '보너스 범위 제거가 이미 실행 중입니다. 잠시 기다려 주세요.' + failed: '섬 검색에 실패했습니다. 콘솔을 확인하세요.' register: parameters: <플레이어> description: 플레이어를 당신이 서있는 주인없는섬의 일원으로 추가합니다 diff --git a/src/main/resources/locales/lv.yml b/src/main/resources/locales/lv.yml index 0fd5b0d40..3a89d7ee6 100644 --- a/src/main/resources/locales/lv.yml +++ b/src/main/resources/locales/lv.yml @@ -250,6 +250,22 @@ commands: success: >- Veiksmīgi samazināts [name]salas aizsardzības rādiuss līdz [total] (+[number]). + removebonus: + parameters: ' [id]' + description: 'noņem bonusa diapazonus no salas (visus vai vienu pēc id)' + no-bonus: 'Šai salai nav noņemamu bonusa diapazonu!' + unknown-bonus: 'Šajā salā nav bonusa diapazona ar id [id]!' + success: 'Noņemti [number] bonusa diapazoni no [name] salas.' + success-id: 'Noņemts bonusa diapazons [id] ([number]) no [name] salas.' + purgebonus: + parameters: '' + description: 'noņem bonusa diapazonu ar doto id no visām salām (piem., pēc papildinājuma, kas to pievienoja, noņemšanas)' + none: 'Nevienai salai nav bonusa diapazona ar id [id]!' + warning: 'Šis noņems bonusa diapazonu [id] no [number] salām.' + success: 'Bonusa diapazons [id] noņemts no [number] salām.' + confirm: 'Lai turpinātu, palaidiet komandu vēlreiz ar confirm.' + in-progress: 'Bonusa diapazonu tīrīšana jau notiek. Lūdzu, uzgaidiet.' + failed: 'Neizdevās skenēt salas. Pārbaudiet konsoli.' register: parameters: description: >- diff --git a/src/main/resources/locales/nl.yml b/src/main/resources/locales/nl.yml index 5f78d16e1..2e1d88a45 100644 --- a/src/main/resources/locales/nl.yml +++ b/src/main/resources/locales/nl.yml @@ -257,6 +257,22 @@ commands: success: >- Succesvol verkleind [name] 's beschermde eilandbereik naar [total] (- [number]) . + removebonus: + parameters: ' [id]' + description: 'verwijdert bonusbereiken van het eiland (alle of één op id)' + no-bonus: 'Dit eiland heeft geen bonusbereiken om te verwijderen!' + unknown-bonus: 'Er is geen bonusbereik met id [id] op dit eiland!' + success: 'Verwijderd: [number] aan bonusbereik van het eiland van [name].' + success-id: 'Bonusbereik [id] ([number]) verwijderd van het eiland van [name].' + purgebonus: + parameters: '' + description: 'verwijdert een bonusbereik met die id van alle eilanden (bijv. na het verwijderen van de addon die het toevoegde)' + none: 'Geen enkel eiland heeft een bonusbereik met id [id]!' + warning: 'Dit verwijdert bonusbereik [id] van [number] eiland(en).' + success: 'Bonusbereik [id] verwijderd van [number] eiland(en).' + confirm: 'Voer de opdracht opnieuw uit met confirm om door te gaan.' + in-progress: 'Er loopt al een bonusbereik-opschoning. Even geduld.' + failed: 'Kon eilanden niet scannen. Controleer de console.' register: parameters: description: registreer speler op het eiland waar je je bevindt diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml index f31a6e50e..49f466dd6 100644 --- a/src/main/resources/locales/pl.yml +++ b/src/main/resources/locales/pl.yml @@ -252,6 +252,22 @@ commands: success: >- Pomyślnie pomniejszono obszar ochrony wyspy gracza [name]na [total] (-[number]). + removebonus: + parameters: ' [id]' + description: 'usuwa zasięgi bonusowe z wyspy (wszystkie lub jeden po id)' + no-bonus: 'Ta wyspa nie ma zasięgów bonusowych do usunięcia!' + unknown-bonus: 'Na tej wyspie nie ma zasięgu bonusowego o id [id]!' + success: 'Usunięto [number] zasięgu bonusowego z wyspy gracza [name].' + success-id: 'Usunięto zasięg bonusowy [id] ([number]) z wyspy gracza [name].' + purgebonus: + parameters: '' + description: 'usuwa zasięg bonusowy o danym id ze wszystkich wysp (np. po usunięciu dodatku, który go dodał)' + none: 'Żadna wyspa nie ma zasięgu bonusowego o id [id]!' + warning: 'To usunie zasięg bonusowy [id] z [number] wysp.' + success: 'Usunięto zasięg bonusowy [id] z [number] wysp.' + confirm: 'Uruchom polecenie ponownie z confirm, aby kontynuować.' + in-progress: 'Czyszczenie zasięgów bonusowych już trwa. Proszę czekać.' + failed: 'Nie udało się przeskanować wysp. Sprawdź konsolę.' register: parameters: description: zarejestruj gracza na niezamieszkanej wyspie, na której jesteś diff --git a/src/main/resources/locales/pt-BR.yml b/src/main/resources/locales/pt-BR.yml index 1257d542f..2a33f3b44 100644 --- a/src/main/resources/locales/pt-BR.yml +++ b/src/main/resources/locales/pt-BR.yml @@ -246,6 +246,22 @@ commands: success: >- Raio de proteção da ilha de [name]reduzido para [total] (-[number]). + removebonus: + parameters: ' [id]' + description: 'remove os alcances bônus da ilha (todos ou um por id)' + no-bonus: 'Esta ilha não tem alcances bônus para remover!' + unknown-bonus: 'Não existe nenhum alcance bônus com o id [id] nesta ilha!' + success: 'Removido [number] de alcance bônus da ilha de [name].' + success-id: 'Alcance bônus [id] ([number]) removido da ilha de [name].' + purgebonus: + parameters: '' + description: 'remove um alcance bônus com esse id de todas as ilhas (ex.: após remover o addon que o adicionou)' + none: 'Nenhuma ilha tem um alcance bônus com o id [id]!' + warning: 'Isto removerá o alcance bônus [id] de [number] ilha(s).' + success: 'Alcance bônus [id] removido de [number] ilha(s).' + confirm: 'Execute o comando novamente com confirm para prosseguir.' + in-progress: 'Uma limpeza de alcances bônus já está em andamento. Aguarde.' + failed: 'Falha ao escanear as ilhas. Verifique o console.' register: parameters: description: registrar jogador para a ilha sem dono em que você está diff --git a/src/main/resources/locales/pt.yml b/src/main/resources/locales/pt.yml index 3b99a1b29..81722216f 100644 --- a/src/main/resources/locales/pt.yml +++ b/src/main/resources/locales/pt.yml @@ -253,6 +253,22 @@ commands: success: >- Diminuido com sucesso [name]área de proteção para [total] (-[number]). + removebonus: + parameters: ' [id]' + description: 'remove os alcances bónus da ilha (todos ou um por id)' + no-bonus: 'Esta ilha não tem alcances bónus para remover!' + unknown-bonus: 'Não existe nenhum alcance bónus com o id [id] nesta ilha!' + success: 'Removido [number] de alcance bónus da ilha de [name].' + success-id: 'Alcance bónus [id] ([number]) removido da ilha de [name].' + purgebonus: + parameters: '' + description: 'remove um alcance bónus com esse id de todas as ilhas (ex.: após remover o addon que o adicionou)' + none: 'Nenhuma ilha tem um alcance bónus com o id [id]!' + warning: 'Isto irá remover o alcance bónus [id] de [number] ilha(s).' + success: 'Alcance bónus [id] removido de [number] ilha(s).' + confirm: 'Execute o comando novamente com confirm para continuar.' + in-progress: 'Já está em curso uma limpeza de alcances bónus. Aguarde.' + failed: 'Falha ao analisar as ilhas. Verifique a consola.' register: parameters: description: registrar jogador na ilha sem dono em que você está diff --git a/src/main/resources/locales/ro.yml b/src/main/resources/locales/ro.yml index d61f6a292..586e827ec 100644 --- a/src/main/resources/locales/ro.yml +++ b/src/main/resources/locales/ro.yml @@ -255,6 +255,22 @@ commands: success: >- Scăderea cu succes a zonei protejate de [name] la [total] (- [number] ) . + removebonus: + parameters: ' [id]' + description: 'elimină razele bonus de pe insulă (toate sau una după id)' + no-bonus: 'Această insulă nu are raze bonus de eliminat!' + unknown-bonus: 'Nu există nicio rază bonus cu id-ul [id] pe această insulă!' + success: 'Eliminat [number] din raza bonus de pe insula lui [name].' + success-id: 'Raza bonus [id] ([number]) eliminată de pe insula lui [name].' + purgebonus: + parameters: '' + description: 'elimină o rază bonus cu acel id de pe toate insulele (de ex. după eliminarea addonului care a adăugat-o)' + none: 'Nicio insulă nu are o rază bonus cu id-ul [id]!' + warning: 'Aceasta va elimina raza bonus [id] de pe [number] insulă(e).' + success: 'Raza bonus [id] eliminată de pe [number] insulă(e).' + confirm: 'Rulează comanda din nou cu confirm pentru a continua.' + in-progress: 'O curățare a razelor bonus este deja în curs. Te rugăm să aștepți.' + failed: 'Scanarea insulelor a eșuat. Verifică consola.' register: parameters: description: înregistrează jucătorul pe insula neproprietată pe care te afli diff --git a/src/main/resources/locales/ru.yml b/src/main/resources/locales/ru.yml index 2730d77db..6d130acc8 100644 --- a/src/main/resources/locales/ru.yml +++ b/src/main/resources/locales/ru.yml @@ -250,6 +250,22 @@ commands: parameters: <игрок> <радиус> success: Радиус острова игрока [name] успешно уменьшен до [total] (-[number]). + removebonus: + parameters: '<игрок> [id]' + description: 'удаляет бонусные радиусы с острова (все или один по id)' + no-bonus: 'На этом острове нет бонусных радиусов для удаления!' + unknown-bonus: 'На этом острове нет бонусного радиуса с id [id]!' + success: 'Удалено [number] бонусного радиуса с острова игрока [name].' + success-id: 'Бонусный радиус [id] ([number]) удалён с острова игрока [name].' + purgebonus: + parameters: '' + description: 'удаляет бонусный радиус с указанным id со всех островов (например, после удаления аддона, который его добавил)' + none: 'Ни на одном острове нет бонусного радиуса с id [id]!' + warning: 'Это удалит бонусный радиус [id] с [number] остров(ов).' + success: 'Бонусный радиус [id] удалён с [number] остров(ов).' + confirm: 'Запустите команду снова с confirm, чтобы продолжить.' + in-progress: 'Очистка бонусных радиусов уже выполняется. Пожалуйста, подождите.' + failed: 'Не удалось просканировать острова. Проверьте консоль.' register: parameters: <игрок> description: регистрирует игрока на острове без владельца, на котором вы находитесь diff --git a/src/main/resources/locales/tr.yml b/src/main/resources/locales/tr.yml index 4cb81dea0..48a39c36e 100644 --- a/src/main/resources/locales/tr.yml +++ b/src/main/resources/locales/tr.yml @@ -246,6 +246,22 @@ commands: success: >- [name] koruma alanı -[number] azaltıldı. Toplam alan [total] + removebonus: + parameters: ' [id]' + description: 'adadan bonus menzilleri kaldırır (tümü veya id ile biri)' + no-bonus: 'Bu adada kaldırılacak bonus menzil yok!' + unknown-bonus: 'Bu adada [id] id''li bir bonus menzil yok!' + success: '[name] adlı oyuncunun adasından [number] bonus menzil kaldırıldı.' + success-id: '[name] adlı oyuncunun adasından [id] ([number]) bonus menzili kaldırıldı.' + purgebonus: + parameters: '' + description: 'belirtilen id''ye sahip bonus menzili tüm adalardan kaldırır (ör. onu ekleyen eklenti kaldırıldıktan sonra)' + none: 'Hiçbir adada [id] id''li bir bonus menzil yok!' + warning: 'Bu işlem bonus menzil [id] öğesini [number] adadan kaldıracak.' + success: 'Bonus menzil [id] [number] adadan kaldırıldı.' + confirm: 'Devam etmek için komutu confirm ile tekrar çalıştırın.' + in-progress: 'Zaten bir bonus menzil temizliği çalışıyor. Lütfen bekleyin.' + failed: 'Adalar taranamadı. Konsolu kontrol edin.' register: parameters: description: Sahipsiz adalara oyuncu ver. diff --git a/src/main/resources/locales/uk.yml b/src/main/resources/locales/uk.yml index 29b8cd3d1..59e3522e9 100644 --- a/src/main/resources/locales/uk.yml +++ b/src/main/resources/locales/uk.yml @@ -224,6 +224,22 @@ commands: description: зменшити діапазон захисту [prefix_island] parameters: <гравець> [[prefix_island] location] success: 'Успішно зменшено діапазон захисту [prefix_island] [name]до [total] (-[number]).' + removebonus: + parameters: '<гравець> [id]' + description: 'видаляє бонусні радіуси з острова (усі або один за id)' + no-bonus: 'На цьому острові немає бонусних радіусів для видалення!' + unknown-bonus: 'На цьому острові немає бонусного радіуса з id [id]!' + success: 'Видалено [number] бонусного радіуса з острова гравця [name].' + success-id: 'Бонусний радіус [id] ([number]) видалено з острова гравця [name].' + purgebonus: + parameters: '' + description: 'видаляє бонусний радіус із вказаним id з усіх островів (наприклад, після видалення аддона, що його додав)' + none: 'На жодному острові немає бонусного радіуса з id [id]!' + warning: 'Це видалить бонусний радіус [id] з [number] острова(ів).' + success: 'Бонусний радіус [id] видалено з [number] острова(ів).' + confirm: 'Запустіть команду знову з confirm, щоб продовжити.' + in-progress: 'Очищення бонусних радіусів уже виконується. Будь ласка, зачекайте.' + failed: 'Не вдалося просканувати острови. Перевірте консоль.' register: parameters: <гравець> description: зареєструвати гравця на безхазяйному [prefix_island], на якому ви стоїте diff --git a/src/main/resources/locales/vi.yml b/src/main/resources/locales/vi.yml index 7845221b2..f9c17eb6c 100644 --- a/src/main/resources/locales/vi.yml +++ b/src/main/resources/locales/vi.yml @@ -249,6 +249,22 @@ commands: success: >- Đã giảm khu vực bảo vệ đảo của [name]xuống [total] ( -'[number]).' + removebonus: + parameters: ' [id]' + description: 'xóa các phạm vi thưởng khỏi đảo (tất cả hoặc một theo id)' + no-bonus: 'Đảo này không có phạm vi thưởng nào để xóa!' + unknown-bonus: 'Không có phạm vi thưởng nào với id [id] trên đảo này!' + success: 'Đã xóa [number] phạm vi thưởng khỏi đảo của [name].' + success-id: 'Đã xóa phạm vi thưởng [id] ([number]) khỏi đảo của [name].' + purgebonus: + parameters: '' + description: 'xóa một phạm vi thưởng có id đó khỏi tất cả các đảo (ví dụ: sau khi gỡ addon đã thêm nó)' + none: 'Không có đảo nào có phạm vi thưởng với id [id]!' + warning: 'Thao tác này sẽ xóa phạm vi thưởng [id] khỏi [number] đảo.' + success: 'Đã xóa phạm vi thưởng [id] khỏi [number] đảo.' + confirm: 'Chạy lại lệnh với confirm để tiếp tục.' + in-progress: 'Đã có một tiến trình xóa phạm vi thưởng đang chạy. Vui lòng đợi.' + failed: 'Không thể quét các đảo. Kiểm tra bảng điều khiển.' register: parameters: description: đăng kí người chơi vào đảo không chủ này diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index 93388462d..3150ffbe2 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -221,6 +221,22 @@ commands: description: 减少岛屿保护范围 parameters: [island location] success: '玩家[name]的岛屿保护范围已减少至[total](-[number]).' + removebonus: + parameters: ' [id]' + description: '移除岛屿的奖励范围(全部,或按id移除一个)' + no-bonus: '该岛屿没有可移除的奖励范围!' + unknown-bonus: '该岛屿上不存在id为[id]的奖励范围!' + success: '已从[name]的岛屿移除[number]的奖励范围.' + success-id: '已从[name]的岛屿移除奖励范围[id]([number]).' + purgebonus: + parameters: '' + description: '从所有岛屿移除指定id的奖励范围(例如,在移除添加它的附属之后)' + none: '没有任何岛屿拥有id为[id]的奖励范围!' + warning: '这将从[number]个岛屿移除奖励范围[id].' + success: '已从[number]个岛屿移除奖励范围[id].' + confirm: '请附带confirm再次执行该命令以继续.' + in-progress: '奖励范围清除已在进行中, 请稍候.' + failed: '扫描岛屿失败, 请检查控制台.' register: parameters: <玩家> description: 将玩家认领到当前的无主岛屿 diff --git a/src/main/resources/locales/zh-HK.yml b/src/main/resources/locales/zh-HK.yml index b372d89f1..928975ebb 100644 --- a/src/main/resources/locales/zh-HK.yml +++ b/src/main/resources/locales/zh-HK.yml @@ -222,6 +222,22 @@ commands: description: 減少島嶼保護範圍 parameters: success: '已將 [name]的島嶼保護範圍減少到 [total] +[number]' + removebonus: + parameters: ' [id]' + description: '移除島嶼的獎勵範圍(全部,或按id移除一個)' + no-bonus: '該島嶼沒有可移除的獎勵範圍!' + unknown-bonus: '該島嶼上不存在id為[id]的獎勵範圍!' + success: '已從[name]的島嶼移除[number]的獎勵範圍.' + success-id: '已從[name]的島嶼移除獎勵範圍[id]([number]).' + purgebonus: + parameters: '' + description: '從所有島嶼移除指定id的獎勵範圍(例如,在移除新增它的附加元件之後)' + none: '沒有任何島嶼擁有id為[id]的獎勵範圍!' + warning: '這將從[number]個島嶼移除獎勵範圍[id].' + success: '已從[number]個島嶼移除獎勵範圍[id].' + confirm: '請附帶confirm再次執行該命令以繼續.' + in-progress: '獎勵範圍清除已在進行中, 請稍候.' + failed: '掃描島嶼失敗, 請檢查主控台.' register: parameters: description: 將玩家註冊到您當前所在的無人島 diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangePurgeBonusCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangePurgeBonusCommandTest.java new file mode 100644 index 000000000..e99021289 --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangePurgeBonusCommandTest.java @@ -0,0 +1,192 @@ +package world.bentobox.bentobox.api.commands.admin.range; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; + +import org.bukkit.entity.Player; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import world.bentobox.bentobox.CommonTestSetup; +import world.bentobox.bentobox.Settings; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.BonusRangeRecord; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.CommandsManager; +import world.bentobox.bentobox.managers.island.IslandCache; +import world.bentobox.bentobox.util.Util; + +/** + * Tests for {@link AdminRangePurgeBonusCommand}. + * + * @author tastybento + */ +class AdminRangePurgeBonusCommandTest extends CommonTestSetup { + + @Mock + private CompositeCommand ac; + @Mock + private User user; + @Mock + private IslandCache islandCache; + @Mock + private Island island2; + + private AdminRangePurgeBonusCommand command; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + Util.setPlugin(plugin); + + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + + Settings s = mock(Settings.class); + when(s.getConfirmationTime()).thenReturn(0); + when(plugin.getSettings()).thenReturn(s); + + Player p = mock(Player.class); + when(user.isOp()).thenReturn(false); + User.setPlugin(plugin); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(p); + when(user.getName()).thenReturn("tastybento"); + + when(ac.getSubCommandAliases()).thenReturn(new HashMap<>()); + when(ac.getWorld()).thenReturn(world); + when(ac.getTopLabel()).thenReturn("bsb"); + + // Two islands in this world carry the "Upgrades" bonus; island2 also has "Other" + when(island.getWorld()).thenReturn(world); + when(island2.getWorld()).thenReturn(world); + when(island.getUniqueId()).thenReturn("island1"); + when(island2.getUniqueId()).thenReturn("island2"); + when(island.getBonusRangeRecord("Upgrades")).thenReturn(Optional.of(new BonusRangeRecord("Upgrades", 50, ""))); + when(island2.getBonusRangeRecord("Upgrades")).thenReturn(Optional.of(new BonusRangeRecord("Upgrades", 50, ""))); + when(island.getBonusRangeRecord("Ghost")).thenReturn(Optional.empty()); + when(island2.getBonusRangeRecord("Ghost")).thenReturn(Optional.empty()); + when(island.getBonusRanges()) + .thenReturn(new ArrayList<>(List.of(new BonusRangeRecord("Upgrades", 50, "")))); + when(island2.getBonusRanges()) + .thenReturn(new ArrayList<>(List.of(new BonusRangeRecord("Upgrades", 50, ""), + new BonusRangeRecord("Other", 10, "")))); + + when(islandCache.getCachedIslands()).thenReturn(List.of(island, island2)); + when(im.getIslandById("island1")).thenReturn(Optional.of(island)); + when(im.getIslandById("island2")).thenReturn(Optional.of(island2)); + when(im.getIslandCache()).thenReturn(islandCache); + + command = new AdminRangePurgeBonusCommand(ac); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + void testCanExecuteNoArgs() { + assertFalse(command.canExecute(user, "purgebonus", Collections.emptyList())); + } + + @Test + void testCanExecuteTooManyArgs() { + assertFalse(command.canExecute(user, "purgebonus", List.of("Upgrades", "now", "extra"))); + } + + @Test + void testCanExecuteBadSecondArg() { + assertFalse(command.canExecute(user, "purgebonus", List.of("Upgrades", "yes"))); + } + + @Test + void testCanExecuteValidId() { + assertTrue(command.canExecute(user, "purgebonus", List.of("Upgrades"))); + } + + @Test + void testCanExecuteValidConfirm() { + assertTrue(command.canExecute(user, "purgebonus", List.of("Upgrades", "confirm"))); + } + + @Test + void testCanExecuteRejectedWhileInPurge() { + command.inPurge = true; + assertFalse(command.canExecute(user, "purgebonus", List.of("Upgrades"))); + verify(user).sendMessage("commands.admin.range.purgebonus.in-progress"); + } + + @Test + void testFindIslandIds() { + List ids = command.findIslandIds(List.of(island, island2), "Upgrades"); + assertEquals(List.of("island1", "island2"), ids); + assertTrue(command.findIslandIds(List.of(island, island2), "Ghost").isEmpty()); + } + + @Test + void testConfirmAppliesPurge() { + // Arrange a pending confirmation as the async scan would have done + command.toBeConfirmed = true; + command.pendingId = "Upgrades"; + command.pendingIslandIds = List.of("island1", "island2"); + + assertTrue(command.execute(user, "purgebonus", List.of("Upgrades", "confirm"))); + + verify(island, times(1)).clearBonusRange("Upgrades"); + verify(island2, times(1)).clearBonusRange("Upgrades"); + verify(user).sendMessage("commands.admin.range.purgebonus.success", "[id]", "Upgrades", "[number]", "2"); + // Pending state cleared + assertFalse(command.toBeConfirmed); + } + + @Test + void testApplyPurgeSkipsIslandsNoLongerCarryingBonus() { + // island2 no longer has the bonus by the time we apply + when(island2.getBonusRangeRecord("Upgrades")).thenReturn(Optional.empty()); + command.applyPurge(user, "Upgrades", List.of("island1", "island2")); + verify(island, times(1)).clearBonusRange("Upgrades"); + verify(island2, never()).clearBonusRange("Upgrades"); + verify(user).sendMessage("commands.admin.range.purgebonus.success", "[id]", "Upgrades", "[number]", "1"); + } + + @Test + void testTabCompleteListsBonusIds() { + List ids = command.tabComplete(user, "purgebonus", List.of("")).orElse(Collections.emptyList()); + assertTrue(ids.contains("Upgrades")); + assertTrue(ids.contains("Other")); + } + + @Test + void testTabCompleteFiltersByPrefix() { + List ids = command.tabComplete(user, "purgebonus", List.of("Up")).orElse(Collections.emptyList()); + assertTrue(ids.contains("Upgrades")); + assertFalse(ids.contains("Other")); + } + + @Test + void testTabCompleteSecondArgSuggestsConfirm() { + List opts = command.tabComplete(user, "purgebonus", List.of("Upgrades", "")) + .orElse(Collections.emptyList()); + assertTrue(opts.contains("confirm")); + } +} diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeRemoveBonusCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeRemoveBonusCommandTest.java new file mode 100644 index 000000000..f2e7abc78 --- /dev/null +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/range/AdminRangeRemoveBonusCommandTest.java @@ -0,0 +1,193 @@ +package world.bentobox.bentobox.api.commands.admin.range; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import org.bukkit.entity.Player; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import world.bentobox.bentobox.CommonTestSetup; +import world.bentobox.bentobox.Settings; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.BonusRangeRecord; +import world.bentobox.bentobox.managers.CommandsManager; +import world.bentobox.bentobox.managers.PlayersManager; +import world.bentobox.bentobox.util.Util; + +/** + * Tests for {@link AdminRangeRemoveBonusCommand}. + * + * @author tastybento + */ +class AdminRangeRemoveBonusCommandTest extends CommonTestSetup { + + @Mock + private CompositeCommand ac; + @Mock + private User user; + @Mock + private PlayersManager pm; + + private AdminRangeRemoveBonusCommand command; + private List bonusRanges; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + + Util.setPlugin(plugin); + + // Command manager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + + // Settings - confirmation time of 0 seconds + Settings s = mock(Settings.class); + when(s.getConfirmationTime()).thenReturn(0); + when(plugin.getSettings()).thenReturn(s); + + // Player + Player p = mock(Player.class); + when(user.isOp()).thenReturn(false); + User.setPlugin(plugin); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(p); + when(user.getName()).thenReturn("tastybento"); + + // Players manager + when(pm.getUUID("tastybento")).thenReturn(uuid); + when(plugin.getPlayers()).thenReturn(pm); + + // Parent command has no aliases and a known top label (needed for confirmation matching) + when(ac.getSubCommandAliases()).thenReturn(new HashMap<>()); + when(ac.getWorld()).thenReturn(world); + when(ac.getTopLabel()).thenReturn("bsb"); + + // Island has a couple of bonus ranges to begin with + bonusRanges = new ArrayList<>(); + BonusRangeRecord level = new BonusRangeRecord("Level", 50, "Reward"); + bonusRanges.add(level); + bonusRanges.add(new BonusRangeRecord("Upgrades", 50, "Upgrade")); + when(island.getBonusRanges()).thenReturn(bonusRanges); + when(island.getBonusRangeRecord("Level")).thenReturn(Optional.of(level)); + when(island.getBonusRangeRecord("Upgrades")).thenReturn(Optional.of(bonusRanges.get(1))); + when(island.getBonusRange("Level")).thenReturn(50); + when(im.getIsland(any(), any(UUID.class))).thenReturn(island); + + command = new AdminRangeRemoveBonusCommand(ac); + } + + @Override + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + void testCanExecuteNoArgs() { + assertFalse(command.canExecute(user, "removebonus", Collections.emptyList())); + } + + @Test + void testCanExecuteTooManyArgs() { + assertFalse(command.canExecute(user, "removebonus", List.of("tastybento", "Level", "extra"))); + } + + @Test + void testCanExecuteUnknownPlayer() { + when(pm.getUUID(any())).thenReturn(null); + assertFalse(command.canExecute(user, "removebonus", List.of("tastybento"))); + verify(user).sendMessage("general.errors.unknown-player", "[name]", "tastybento"); + } + + @Test + void testCanExecuteNoIsland() { + when(im.getIsland(any(), any(UUID.class))).thenReturn(null); + assertFalse(command.canExecute(user, "removebonus", List.of("tastybento"))); + verify(user).sendMessage("general.errors.player-has-no-island"); + } + + @Test + void testCanExecuteNoBonus() { + bonusRanges.clear(); + assertFalse(command.canExecute(user, "removebonus", List.of("tastybento"))); + verify(user).sendMessage("commands.admin.range.removebonus.no-bonus"); + } + + @Test + void testCanExecuteUnknownBonusId() { + when(island.getBonusRangeRecord("Nope")).thenReturn(Optional.empty()); + assertFalse(command.canExecute(user, "removebonus", List.of("tastybento", "Nope"))); + verify(user).sendMessage("commands.admin.range.removebonus.unknown-bonus", "[id]", "Nope"); + } + + @Test + void testCanExecuteSuccessAll() { + assertTrue(command.canExecute(user, "removebonus", List.of("tastybento"))); + } + + @Test + void testCanExecuteSuccessId() { + assertTrue(command.canExecute(user, "removebonus", List.of("tastybento", "Level"))); + } + + @Test + void testExecuteAsksConfirmationDoesNotRemoveYet() { + assertTrue(command.canExecute(user, "removebonus", List.of("tastybento"))); + assertTrue(command.execute(user, "removebonus", List.of("tastybento"))); + // Nothing removed until confirmed + verify(island, never()).clearAllBonusRanges(); + verify(island, never()).clearBonusRange(any()); + verify(user).sendMessage("commands.confirmation.confirm", "[seconds]", "0"); + } + + @Test + void testRemoveAllBonusRanges() { + assertTrue(command.canExecute(user, "removebonus", List.of("tastybento"))); + command.removeBonusRanges(user, "tastybento"); + verify(island, times(1)).clearAllBonusRanges(); + verify(island, never()).clearBonusRange(any()); + verify(user).sendMessage("commands.admin.range.removebonus.success", "[number]", "100", "[name]", + "tastybento"); + } + + @Test + void testRemoveBonusRangeById() { + assertTrue(command.canExecute(user, "removebonus", List.of("tastybento", "Level"))); + command.removeBonusRanges(user, "tastybento"); + verify(island, times(1)).clearBonusRange("Level"); + verify(island, never()).clearAllBonusRanges(); + verify(user).sendMessage("commands.admin.range.removebonus.success-id", "[id]", "Level", "[number]", "50", + "[name]", "tastybento"); + } + + @Test + void testTabCompleteEmptyArgs() { + assertTrue(command.tabComplete(user, "removebonus", Collections.emptyList()).isEmpty()); + } + + @Test + void testTabCompleteBonusIds() { + List ids = command.tabComplete(user, "removebonus", List.of("tastybento", "L")) + .orElse(Collections.emptyList()); + assertTrue(ids.contains("Level")); + } +}