Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package world.bentobox.bentobox.api.commands.admin;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;

import org.bukkit.Location;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import world.bentobox.bentobox.api.commands.CompositeCommand;
Expand All @@ -16,14 +21,30 @@
import world.bentobox.bentobox.util.Util;

/**
* @author tastybento
* Sets a player's rank on an island directly, which covers both promotion and demotion.
* <p>
* Unlike the player-facing {@code /island team promote} and {@code demote} commands, this does
* not step one rank at a time and does not depend on the rank of the admin running it. It is
* intentionally not restricted to in-game players so that server staff can manage teams from the
* console. The island is identified in one of three ways:
* <ul>
* <li>nothing - the island the target is a team member of, if there is exactly one</li>
* <li>the name of the island's owner</li>
* <li>the island's centre as {@code x,y,z}</li>
* </ul>
* Any rank between visitor and owner may be set, so this also grants or revokes coop and trusted
* status. Owner rank is refused - ownership is transferred with {@code setowner}, which keeps the
* island's owner field and the rank map in step.
*
* @author tastybento
*/
public class AdminSetrankCommand extends CompositeCommand {

private static final String RANK_PREFIX = "ranks.";

private int rankValue;
private @Nullable UUID targetUUID;
private @Nullable UUID ownerUUID;
private @Nullable Island island;

public AdminSetrankCommand(CompositeCommand adminCommand) {
super(adminCommand, "setrank");
Expand All @@ -39,113 +60,197 @@

@Override
public boolean canExecute(User user, String label, List<String> args) {
// Syntax: <player> <rank> [island owner | x,y,z]
if (args.size() != 2 && args.size() != 3) {
// Show help
showHelp(this, user);
return false;
}
// Get target player
// Target
targetUUID = Util.getUUID(args.getFirst());
if (targetUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.getFirst());
return false;
}
// Get rank
rankValue = RanksManager.getInstance().getRanks().entrySet().stream()
.filter(r -> user.getTranslation(r.getKey()).equalsIgnoreCase(args.get(1))).findFirst()
.map(Map.Entry::getValue).orElse(-999);
if (rankValue < RanksManager.BANNED_RANK) {
user.sendMessage("commands.admin.setrank.unknown-rank");
// Rank
Optional<Integer> rank = parseRank(user, args.get(1));
if (rank.isEmpty()) {
user.sendMessage("commands.admin.setrank.unknown-rank", TextVariables.RANK, args.get(1), "[ranks]",
String.join(", ", getAllowedRankNames()));
return false;
}
rankValue = rank.get();
if (rankValue <= RanksManager.VISITOR_RANK) {
user.sendMessage("commands.admin.setrank.not-possible");
return false;
}
if (rankValue >= RanksManager.OWNER_RANK) {
user.sendMessage("commands.admin.setrank.cannot-set-owner");

Check failure on line 87 in src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSetrankCommand.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "commands.admin.setrank.cannot-set-owner" 3 times.

See more on https://sonarcloud.io/project/issues?id=BentoBoxWorld_BentoBox&issues=AaB88IEhvqoWJCHlkMV6&open=AaB88IEhvqoWJCHlkMV6&pullRequest=3079
return false;
}
// Island
island = args.size() == 2 ? findTargetIsland(user) : findNamedIsland(user, args.get(2));
if (island == null) {
return false;
}
if (targetUUID.equals(island.getOwner())) {
user.sendMessage("commands.admin.setrank.cannot-set-owner");
return false;
}
if (island.getRank(targetUUID) == rankValue) {
user.sendMessage("commands.admin.setrank.already-rank", TextVariables.NAME, args.getFirst(),
TextVariables.RANK, user.getTranslation(RanksManager.getInstance().getRank(rankValue)));
return false;
}
return true;
}

if (args.size() == 2) {
// We want to change the player's rank on the island he is part of.
@Override
public boolean execute(User user, String label, List<String> args) {
Objects.requireNonNull(island);
Objects.requireNonNull(targetUUID);
User target = User.getInstance(targetUUID);
int currentRank = island.getRank(targetUUID);
String ownerName = getPlayers().getName(island.getOwner());

// Check if the target is part of an island
if (!getIslands().hasIsland(getWorld(), targetUUID) && !getPlugin().getIslands().inTeam(getWorld(), targetUUID)) {
user.sendMessage("general.errors.player-has-no-island");
return false;
}
} else {
// We want to change the player's rank on the island of the specified owner.
island.setRank(targetUUID, rankValue);
IslandEvent.builder().island(island).involvedPlayer(targetUUID).admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE).rankChange(currentRank, rankValue).build();

ownerUUID = getPlayers().getUUID(args.get(2));
if (ownerUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(2));
return false;
}
user.sendMessage("commands.admin.setrank.rank-set", "[from]",
user.getTranslation(RanksManager.getInstance().getRank(currentRank)), "[to]",
user.getTranslation(RanksManager.getInstance().getRank(rankValue)), TextVariables.NAME, ownerName);
if (target.isOnline()) {
target.sendMessage("commands.admin.setrank.admin-changed-rank", TextVariables.RANK,
target.getTranslation(RanksManager.getInstance().getRank(rankValue)), TextVariables.NAME,
ownerName);
}
return true;
}

if (!getPlugin().getIslands().hasIsland(getWorld(), ownerUUID)) {
user.sendMessage("general.errors.player-is-not-owner", TextVariables.NAME, args.get(2));
return false;
/**
* Resolves a rank argument. Accepts the rank keyword ({@code member}, {@code sub-owner}, or any
* addon-registered rank's reference without the {@code ranks.} prefix), the rank's translated
* name in the caller's locale, or the numeric rank value.
* @return the rank value, if the argument names any known rank
*/
private Optional<Integer> parseRank(User user, String arg) {
String wanted = arg.toLowerCase(Locale.ROOT);
for (Map.Entry<String, Integer> en : RanksManager.getInstance().getRanks().entrySet()) {
String translated = Util.stripColor(user.getTranslation(en.getKey())).toLowerCase(Locale.ROOT);
if (wanted.equals(keyword(en.getKey())) || wanted.equals(translated)
|| wanted.equals(String.valueOf(en.getValue()))) {
return Optional.of(en.getValue());
}
}
return Optional.empty();
}

return true;
private static String keyword(String reference) {
String key = reference.startsWith(RANK_PREFIX) ? reference.substring(RANK_PREFIX.length()) : reference;
return key.toLowerCase(Locale.ROOT);
}

@Override
public boolean execute(User user, String label, List<String> args) {
assert targetUUID != null;
User target = User.getInstance(targetUUID);
Island island;
if (ownerUUID != null) {
island = getIslands().getIsland(getWorld(), ownerUUID);
} else {
island = getIslands().getIsland(getWorld(), targetUUID);
/**
* @return the keywords of every rank this command can set, lowest first
*/
private static List<String> getAllowedRankNames() {
return RanksManager.getInstance().getRanks().entrySet().stream()
.filter(en -> en.getValue() > RanksManager.VISITOR_RANK && en.getValue() < RanksManager.OWNER_RANK)
.sorted(Map.Entry.comparingByValue()).map(en -> keyword(en.getKey())).toList();
}

/**
* No island was named: use the one team island the target belongs to but does not own.
*/
private @Nullable Island findTargetIsland(User user) {
List<Island> memberOf = getIslands().getIslands(getWorld(), targetUUID).stream()
.filter(i -> !Objects.equals(targetUUID, i.getOwner())).toList();
if (memberOf.isEmpty()) {
if (getIslands().hasIsland(getWorld(), targetUUID)) {
user.sendMessage("commands.admin.setrank.cannot-set-owner");
} else {
user.sendMessage("general.errors.player-has-no-island");
}
return null;
}
if (island == null) {
user.sendMessage("general.errors.player-has-no-island");
return false;
if (memberOf.size() > 1) {
user.sendMessage("commands.admin.unregister.errors.player-has-more-than-one-island");
memberOf.forEach(i -> user.sendMessage("commands.admin.unregister.errors.specify-island-location",
TextVariables.XYZ, Util.xyz(i.getCenter().toVector())));
return null;
}
int currentRank = island.getRank(target);
island.setRank(target, rankValue);
IslandEvent.builder()
.island(island)
.involvedPlayer(targetUUID)
.admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(currentRank, rankValue)
.build();

String ownerName;
if (ownerUUID != null) {
ownerName = getPlayers().getName(ownerUUID);
} else {
ownerName = target.getName();
}
user.sendMessage("commands.admin.setrank.rank-set",
"[from]", user.getTranslation(RanksManager.getInstance().getRank(currentRank)), "[to]",
user.getTranslation(RanksManager.getInstance().getRank(rankValue)),
TextVariables.NAME, ownerName);
return true;
return memberOf.getFirst();
}

@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
// Return the player names
if (args.size() == 2) {
return Optional.of(Util.getOnlinePlayerList(user));
/**
* An island was named, either by its x,y,z centre or by the name of its owner.
*/
private @Nullable Island findNamedIsland(User user, String name) {
Optional<Location> coords = parseXYZ(name);
if (coords.isPresent()) {
Optional<Island> at = getIslands().getIslandAt(coords.get());
if (at.isEmpty()) {
user.sendMessage("commands.admin.unregister.errors.unknown-island-location");
return null;
}
return at.get();
}
UUID ownerUUID = Util.getUUID(name);
if (ownerUUID == null) {
user.sendMessage("general.errors.unknown-player", TextVariables.NAME, name);
return null;
}
List<Island> owned = getIslands().getIslands(getWorld(), ownerUUID).stream()
.filter(i -> ownerUUID.equals(i.getOwner())).toList();
if (owned.isEmpty()) {
user.sendMessage("general.errors.player-is-not-owner", TextVariables.NAME, name);
return null;
}
if (owned.size() > 1) {
// Concurrent islands: the owner alone is ambiguous, so ask for the centre
user.sendMessage("commands.admin.unregister.errors.player-has-more-than-one-island");
owned.forEach(i -> user.sendMessage("commands.admin.unregister.errors.specify-island-location",
TextVariables.XYZ, Util.xyz(i.getCenter().toVector())));
return null;
}
return owned.getFirst();
}

// Return the ranks
if (args.size() == 3) {
return Optional.of(RanksManager.getInstance().getRanks()
.entrySet().stream()
.filter(entry -> entry.getValue() > RanksManager.VISITOR_RANK)
.map(entry -> user.getTranslation(entry.getKey())).toList());
/**
* @return a location in this command's world if the argument is in {@code x,y,z} form
*/
private Optional<Location> parseXYZ(String arg) {
String[] parts = arg.split(",");
if (parts.length != 3) {
return Optional.empty();
}
try {
return Optional.of(new Location(getWorld(), Integer.parseInt(parts[0].trim()),
Integer.parseInt(parts[1].trim()), Integer.parseInt(parts[2].trim())));
} catch (NumberFormatException e) {

Check warning on line 230 in src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSetrankCommand.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace "e" with an unnamed pattern.

See more on https://sonarcloud.io/project/issues?id=BentoBoxWorld_BentoBox&issues=AaB88IEhvqoWJCHlkMV7&open=AaB88IEhvqoWJCHlkMV7&pullRequest=3079
return Optional.empty();
}
}

// Return the player names again for the optional island owner argument
if (args.size() == 4) {
@Override
public Optional<List<String>> tabComplete(User user, String alias, @NonNull List<String> args) {
String lastArg = !args.isEmpty() ? args.getLast() : "";
if (args.size() == 1) {
return Optional.of(Util.getOnlinePlayerList(user));
}

if (args.size() == 2) {
return Optional.of(Util.tabLimit(getAllowedRankNames(), lastArg));
}
if (args.size() == 3) {
// Island owners, plus the centres of the islands the target already belongs to
List<String> options = new ArrayList<>(Util.getOnlinePlayerList(user));
UUID targetId = getPlayers().getUUID(args.getFirst());
if (targetId != null) {
getIslands().getIslands(getWorld(), targetId).stream().map(i -> Util.xyz(i.getCenter().toVector()))
.forEach(options::add);
}
return Optional.of(Util.tabLimit(options, lastArg));
}
return Optional.empty();
}
}
13 changes: 7 additions & 6 deletions src/main/resources/locales/cs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -383,13 +383,14 @@ commands:
description: z铆skat hodnost hr谩膷e na jejich ostrov臎 nebo na ostrov臎 vlastn铆ka
rank-is: '<green>Hodnost je </green><aqua>[rank] </aqua><green>na ostrov臎 </green><aqua>[name]</aqua><green>.</green>'
setrank:
parameters: <player> <rank> [island owner]
description: nastavit hodnot hr谩膷e na jejich ostrov臎 nebo na ostrov臎 vlastn铆ka
unknown-rank: '<red>Nezn谩m谩 hodnost!</red>'
parameters: <hr谩膷> <hodnost> [vlastn铆k [prefix_island]u | x,y,z]
description: 'pov媒拧铆 nebo degraduje hr谩膷e na hodnost na jeho t媒mov茅m [prefix_island]u, nebo na [prefix_island]u ur膷en茅m jm茅nem vlastn铆ka 膷i st艡edem x,y,z'
unknown-rank: '<red>Nezn谩m谩 hodnost [rank]. Vyberte jednu z: </red><yellow>[ranks]</yellow>'
not-possible: '<red>Hodnost mus铆 b媒t vy拧拧铆, ne啪 visitor.</red>'
rank-set: >-
<green>Hodnost nastavena z </green><aqua>[from] </aqua><green>na </green><aqua>[to] </aqua><green>na ostrov臎 </green><aqua>[name]</aqua><green></green>
.
cannot-set-owner: '<red>Hodnost vlastn铆ka nelze nastavit ani zm臎nit. Pou啪ijte </red><yellow>setowner</yellow><red> k p艡evodu vlastnictv铆.</red>'
already-rank: '<red>[name] u啪 m谩 hodnost [rank] na tomto [prefix_island]u.</red>'
rank-set: '<green>Hodnost nastavena z </green><aqua>[from] </aqua><green>na </green><aqua>[to] </aqua><green>na ostrov臎 </green><aqua>[name]</aqua><green></green> .'
admin-changed-rank: '<gold>Admin nastavil tvou hodnost na [prefix_island]u hr谩膷e </gold><aqua>[name]</aqua><gold> na </gold><aqua>[rank]</aqua><gold>.</gold>'
setprotectionlocation:
parameters: '[x y z sou艡adnice]'
description: >-
Expand Down
11 changes: 6 additions & 5 deletions src/main/resources/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -407,13 +407,14 @@ commands:
description: Den Rang eines Spielers auf seiner Insel erhalten
rank-is: '<green>Der Rang ist [rank] auf ihrer Insel.</green>'
setrank:
parameters: <player> <rank> [Insels Besitzer]
description: >-
Den Rang eines Spielers auf seiner Insel oder der Insel des Besitzers
festlegen
unknown-rank: '<red>Unbekannter Rang!</red>'
parameters: <Spieler> <Rang> [[prefix_island]-Besitzer | x,y,z]
description: 'bef枚rdert oder degradiert einen Spieler auf einen Rang auf seiner Team-[prefix_island] oder auf der [prefix_island], die 眉ber den Besitzer oder das x,y,z-Zentrum angegeben wird'
unknown-rank: '<red>Unbekannter Rang [rank]. W盲hle einen von: </red><yellow>[ranks]</yellow>'
not-possible: '<red>Der Rang muss h枚her sein als Besucher.</red>'
cannot-set-owner: '<red>Der Rang des Besitzers kann nicht gesetzt oder ge盲ndert werden. Nutze </red><yellow>setowner</yellow><red>, um das Eigentum zu 眉bertragen.</red>'
already-rank: '<red>[name] hat auf dieser [prefix_island] bereits den Rang [rank].</red>'
rank-set: '<green>Rang von</green><aqua>[from]</aqua><green>auf</green><aqua>[to]</aqua><green>gesetzt f眉r</green><aqua>[name]</aqua><green>''s Insel.</green>'
admin-changed-rank: '<gold>Ein Admin hat deinen Rang auf der [prefix_island] von </gold><aqua>[name]</aqua><gold> auf </gold><aqua>[rank]</aqua><gold> gesetzt.</gold>'
setprotectionlocation:
parameters: '[x y z Koordinaten]'
description: >-
Expand Down
11 changes: 7 additions & 4 deletions src/main/resources/locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -362,12 +362,15 @@ commands:
of the owner
rank-is: '<green>Rank is </green><aqua>[rank] </aqua><green>on </green><aqua>[name]</aqua><green>''s [prefix_island].</green>'
setrank:
parameters: <player> <rank> [[prefix_island] owner]
description: set a player's rank on their [prefix_island] or the [prefix_island]
of the owner
unknown-rank: '<red>Unknown rank!</red>'
parameters: <player> <rank> [[prefix_island] owner | x,y,z]
description: >-
promote or demote a player to a rank on their team [prefix_island], or on the [prefix_island] named by its owner or x,y,z centre
unknown-rank: '<red>Unknown rank [rank]. Choose one of: </red><yellow>[ranks]</yellow>'
not-possible: '<red>Rank must be higher than visitor.</red>'
cannot-set-owner: '<red>You cannot set or change the owner''s rank. Use </red><yellow>setowner</yellow><red> to transfer ownership instead.</red>'
already-rank: '<red>[name] is already [rank] on this [prefix_island].</red>'
rank-set: '<green>Rank set from </green><aqua>[from] </aqua><green>to </green><aqua>[to] </aqua><green>on </green><aqua>[name]</aqua><green>''s [prefix_island].</green>'
admin-changed-rank: '<gold>An admin set your rank on </gold><aqua>[name]</aqua><gold>''s [prefix_island] to </gold><aqua>[rank]</aqua><gold>.</gold>'
setprotectionlocation:
parameters: '[x y z coords]'
description: set current location or [x y z] as center of [prefix_island]'s
Expand Down
Loading
Loading