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
3 changes: 3 additions & 0 deletions src/main/java/net/tfminecraft/rpcharacters/RPCharacters.java
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,9 @@ public void registerListeners() {
getServer().getPluginManager().registerEvents(classPickListener, this);
getCommand("class").setExecutor(classPickListener);
getCommand("subclass").setExecutor(classPickListener);
for (String root : java.util.List.of("class", "subclass", "players", "will")) {
getCommand(root).setTabCompleter((sender, command, label, args) -> java.util.List.of());
}
getServer().getPluginManager().registerEvents(clueInputManager, this);
getServer().getPluginManager().registerEvents(placeClueManager, this);
getServer().getPluginManager().registerEvents(magnifyingGlassListener, this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ private boolean handleChannelSwitch(Player player, String label, String[] args)
.replace("{channels}", String.join(", ", Cache.chatSwitchableChannels)));
return true;
}
if (!channel.getUsePermission().isBlank() && !player.hasPermission(channel.getUsePermission())) {
RPTexts.send(player, RPTexts.ERROR + "You cannot use this chat channel.");
return true;
}

String current = ChatChannelPreferenceManager.get().getSwitchedChannelId(player);
if (channelId.equals(current) || (current == null && channelId.equals(Cache.chatDefaultChannel))) {
Expand Down Expand Up @@ -125,16 +129,20 @@ private static String resolveChannelId(String input) {

@Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
if (args.length != 1) {
if (!(sender instanceof Player) || args.length != 1) {
return List.of();
}
String name = command.getName().toLowerCase(Locale.ROOT);
List<String> options = CHANNEL_COMMAND.equals(name)
List<String> options = CHANNEL_COMMAND.equals(name) && Cache.chatChannelSwitcherEnabled
? Cache.chatSwitchableChannels
: TOGGLE_COMMAND.equals(name) ? Cache.chatToggleableChannels : List.of();
: TOGGLE_COMMAND.equals(name) && Cache.chatChannelTogglerEnabled ? Cache.chatToggleableChannels : List.of();
String prefix = args[0].toLowerCase(Locale.ROOT);
return options.stream()
.filter(option -> option.startsWith(prefix))
.filter(option -> ChatLoader.getChannel(option) != null)
.filter(option -> !CHANNEL_COMMAND.equals(name)
|| ChatLoader.getChannel(option).getUsePermission().isBlank()
|| sender.hasPermission(ChatLoader.getChannel(option).getUsePermission()))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
.filter(option -> option.toLowerCase(Locale.ROOT).startsWith(prefix))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public static void sync(Plugin plugin) {
for (String label : labels) {
registerCommand(plugin, label);
}
// Config reload also changes permissions on existing labels in clients' command trees.
Bukkit.getOnlinePlayers().forEach(org.bukkit.entity.Player::updateCommands);
}

private static void registerCommand(Plugin plugin, String label) {
Expand All @@ -40,6 +42,10 @@ private static void registerCommand(Plugin plugin, String label) {
PluginCommand command = constructor.newInstance(normalized, plugin);
command.setExecutor(EXECUTOR);
command.setDescription("RP chat channel");
ChatChannel channel = ChatLoader.getChannel(ChatLoader.resolveChannelFromCommand(normalized));
if (channel != null && !channel.getUsePermission().isBlank()) {
command.setPermission(channel.getUsePermission());
}
Bukkit.getCommandMap().register(plugin.getName().toLowerCase(Locale.ROOT), command);
registered.add(command);
} catch (ReflectiveOperationException | RuntimeException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package net.tfminecraft.rpcharacters.estate;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Locale;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
Expand Down Expand Up @@ -45,13 +47,11 @@ public boolean onCommand(CommandSender sender, Command command, String label, St
}
if (args.length < 2) return reply(sender, "Name the estate: a death ID, character ID or dead character's name.");
// The longest run of words naming an estate wins, so two-word names work before further arguments.
int end = args.length;
Optional<EstateRecord> found = Optional.empty();
while (end > 1 && (found = staff.find(String.join(" ", Arrays.copyOfRange(args, 1, end)))).isEmpty()) end--;
if (found.isEmpty()) return reply(sender, "No estate matches " + args[1] + ".");
EstateRecord record = found.get();
Resolved resolved = resolve(args, args.length, staff::find);
if (resolved == null) return reply(sender, "No estate matches " + args[1] + ".");
EstateRecord record = resolved.record();
String by = sender.getName();
String[] rest = Arrays.copyOfRange(args, end, args.length);
String[] rest = Arrays.copyOfRange(args, resolved.end(), args.length);
return switch (sub) {
case "show" -> {
staff.show(record).forEach(sender::sendMessage);
Expand Down Expand Up @@ -86,13 +86,78 @@ private static boolean reply(CommandSender sender, String message) {
return true;
}

private record Resolved(EstateRecord record, int end) {}

private static Resolved resolve(String[] args, int end, Function<String, Optional<EstateRecord>> lookup) {
while (end > 1) {
Optional<EstateRecord> found = lookup.apply(String.join(" ", Arrays.copyOfRange(args, 1, end)));
if (found.isPresent()) return new Resolved(found.get(), end);
end--;
}
return null;
}

@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (!sender.hasPermission(PERMISSION)) return args.length == 1 ? List.of("collect") : List.of();
if (args.length == 1) return STAFF.stream().filter(s -> s.startsWith(args[0].toLowerCase(java.util.Locale.ROOT))).toList();
if (args.length == 2 && args[0].equalsIgnoreCase("list")) return FILTERS;
if (args.length == 3 && args[0].equalsIgnoreCase("assign")) return List.of("money", "property", "guildlocks", "all");
if (args.length == 3 && args[0].equalsIgnoreCase("decide")) return List.of("--no-guild");
return List.of();
if (args.length == 0) return List.of();
String prefix = args[args.length - 1];
if (!sender.hasPermission(PERMISSION)) return sender instanceof Player && args.length == 1
? filter(List.of("collect"), prefix) : List.of();
if (args.length == 1) return filter(STAFF.stream()
.filter(sub -> !sub.equals("collect") || sender instanceof Player).toList(), prefix);
String sub = args[0].toLowerCase(Locale.ROOT);
if (sub.equals("collect") && !(sender instanceof Player)) return List.of();
if (sub.equals("list")) return args.length == 2 ? filter(FILTERS, prefix) : List.of();
if (!STAFF.contains(sub)) return List.of();

List<EstateRecord> records = staff.records();
List<String> names = new ArrayList<>();
if (args.length == 2) records.forEach(record -> names.add(record.deathId.toString()));
String preceding = args.length == 2 ? "" : String.join(" ", Arrays.copyOfRange(args, 1, args.length - 1)) + " ";
String typed = (preceding + prefix).toLowerCase(Locale.ROOT);
for (EstateRecord record : records) {
if (record.name.toLowerCase(Locale.ROOT).startsWith(typed)) names.add(record.name.substring(preceding.length()));
}
if (!names.isEmpty()) return filter(names, prefix);

// The current token is still being replaced; only completed words can consume the estate key.
Resolved resolved = resolve(args, args.length - 1, key -> records.stream()
.filter(record -> record.deathId.toString().equalsIgnoreCase(key)
|| record.testator.characterId().toString().equalsIgnoreCase(key)
|| record.name.equalsIgnoreCase(key))
.reduce((older, newer) -> newer));
if (resolved == null) return List.of();
EstateRecord record = resolved.record();
int position = args.length - 1 - resolved.end();
List<String> options = new ArrayList<>();
if (position == 0) {
if (sub.equals("decide") && record.state == net.tfminecraft.tlibs.character.estate.EstateState.OPEN) {
options.add("--no-guild");
} else if (sub.equals("assign") && record.settling()) {
var decision = record.decision;
if (assignable(decision.money())) options.add("money");
if (assignable(decision.property())) options.add("property");
if (assignable(decision.guildLocks())) options.add("guildlocks");
if (!options.isEmpty()) options.add("all");
} else if ((sub.equals("retry") || sub.equals("waive")) && record.settling()) {
record.participants.forEach((id, report) -> {
if (report.outcome().equals("BLOCKED")
&& (!sub.equals("waive") || report.revision() == record.decision.revision())) options.add(id);
});
}
} else if (position == 1 && sub.equals("assign") && record.settling()
&& args[resolved.end()].equalsIgnoreCase("money") && record.decision.money().recipient().isFrozen()) {
options.add("guild");
}
return filter(options, prefix);
}

private static boolean assignable(EstateRecord.Part part) {
return part.recipient().isWaiting() || part.recipient().isFrozen();
}

private static List<String> filter(List<String> options, String prefix) {
String lower = prefix.toLowerCase(Locale.ROOT);
return options.stream().filter(option -> option.toLowerCase(Locale.ROOT).startsWith(lower)).distinct().toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ Optional<EstateRecord> find(String key) {
}
}

/** Loaded records only: no participant, character-directory or persistence calls. */
List<EstateRecord> records() {
return List.copyOf(book.all());
}

String hold(EstateRecord record, String staff) {
if (record.state != EstateState.OPEN) return "Only an open estate can be held; this one is " + record.state + ".";
return change(record, staff, "held", next -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import java.util.Locale;
import java.util.stream.Collectors;

import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.profess.PlayerClass;

import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
Expand All @@ -14,6 +17,7 @@

import net.tfminecraft.rpcharacters.Cache;
import net.tfminecraft.rpcharacters.classpick.ClassPickReset;
import net.tfminecraft.rpcharacters.enums.Status;
import net.tfminecraft.rpcharacters.loaders.TraitLoader;
import net.tfminecraft.rpcharacters.managers.PlayerManager;
import net.tfminecraft.rpcharacters.objects.PlayerData;
Expand Down Expand Up @@ -54,8 +58,8 @@ public List<String> onTabComplete(CommandSender sender, Command cmd, String labe
completions.add("clues");
completions.add("wardrobe");
completions.add("party");
completions.add("injure");
completions.addAll(PERSONA_SUBCOMMANDS);
if (Permissions.isAdmin(sender) || sender.hasPermission("rpchar.injure")) completions.add("injure");
PERSONA_SUBCOMMANDS.stream().filter(sub -> canUsePersona(sender, sub)).forEach(completions::add);
if (Permissions.isAdmin(sender)) {
completions.add("admin");
completions.add("reload");
Expand Down Expand Up @@ -89,6 +93,11 @@ public List<String> onTabComplete(CommandSender sender, Command cmd, String labe
}

String sub = args[0].toLowerCase(Locale.ROOT);
if (List.of("setclass", "seteighteen", "skipcooldown", "addtrait", "removetrait", "permakill")
.contains(sub) && !Permissions.isAdmin(sender)) return Collections.emptyList();
if (sub.equals("menu") && !Permissions.isAdmin(sender)) return Collections.emptyList();
if (sub.equals("injure") && !Permissions.isAdmin(sender)
&& !sender.hasPermission("rpchar.injure")) return Collections.emptyList();
if (CharCommand.isPersonaSubcommand(sub)) {
return completePersona(sender, sub, args);
}
Expand Down Expand Up @@ -165,7 +174,7 @@ public List<String> onTabComplete(CommandSender sender, Command cmd, String labe
completions.add(ClassPickReset.SUBCOMMAND);
}
} else if (args[0].equalsIgnoreCase("addtrait") || args[0].equalsIgnoreCase("removetrait")
|| args[0].equalsIgnoreCase("injure")) {
|| args[0].equalsIgnoreCase("injure") || sub.equals("permakill")) {
for (Player online : Bukkit.getOnlinePlayers()) {
completions.add(online.getName());
}
Expand Down Expand Up @@ -217,7 +226,11 @@ public List<String> onTabComplete(CommandSender sender, Command cmd, String labe
} else if (args[0].equalsIgnoreCase("sethidden") && sender.hasPermission(Cache.personaCharacterHiddenPermission)) {
completions.addAll(CLEAR);
} else if (args[0].equalsIgnoreCase("setclass")) {
completions.add("className");
if (MMOCore.plugin != null && MMOCore.plugin.classManager != null) {
for (PlayerClass playerClass : MMOCore.plugin.classManager.getAll()) {
completions.add(playerClass.getId());
}
}
} else if (args[0].equalsIgnoreCase("seteighteen") && Permissions.isAdmin(sender)) {
completions.add("true");
completions.add("false");
Expand All @@ -238,54 +251,36 @@ public List<String> onTabComplete(CommandSender sender, Command cmd, String labe
}
}
} else if (args[0].equalsIgnoreCase("addtrait") || args[0].equalsIgnoreCase("removetrait")) {
for (Trait trait : TraitLoader.get()) {
completions.add(trait.getId());
Player target = Bukkit.getPlayerExact(args[1]);
PlayerData data = target == null ? null : PlayerManager.get(target);
if (data != null && data.hasActiveCharacter()) {
List<Trait> owned = data.getActiveCharacter().getTraits();
for (Trait trait : sub.equals("removetrait") ? owned : TraitLoader.get()) {
if (sub.equals("removetrait") || owned.stream()
.noneMatch(current -> current.getId().equalsIgnoreCase(trait.getId()))) {
completions.add(trait.getId());
}
}
}
} else if (args[0].equalsIgnoreCase("admin") && Permissions.isAdmin(sender)
&& (args[1].equalsIgnoreCase("injure") || args[1].equalsIgnoreCase("permakill"))) {
for (Player online : Bukkit.getOnlinePlayers()) {
completions.add(online.getName());
}
} else if (args[0].equalsIgnoreCase("injure") && Permissions.isAdmin(sender)) {
Player target = Bukkit.getPlayerExact(args[1]);
if (target != null) {
PlayerData pd = PlayerManager.get(target);
if (pd != null) {
for (RPCharacter character : pd.getCharacters()) {
if (character.getSlug() != null) {
completions.add(character.getSlug());
}
if (character.getName() != null) {
completions.add(character.getName());
}
}
}
}
completions.add("permanent");
} else if ((sub.equals("injure") || sub.equals("permakill")) && Permissions.isAdmin(sender)) {
completions.addAll(characterIds(args[1]));
if (sub.equals("injure")) completions.add("permanent");
}

return filter(completions, args[2]);
}

if (args.length == 4
&& args[0].equalsIgnoreCase("admin")
&& args[1].equalsIgnoreCase("injure")
&& sub.equals("admin")
&& (args[1].equalsIgnoreCase("injure") || args[1].equalsIgnoreCase("permakill"))
&& Permissions.isAdmin(sender)) {
Player target = Bukkit.getPlayerExact(args[2]);
if (target != null) {
PlayerData pd = PlayerManager.get(target);
if (pd != null) {
for (RPCharacter character : pd.getCharacters()) {
if (character.getSlug() != null) {
completions.add(character.getSlug());
}
if (character.getName() != null) {
completions.add(character.getName());
}
}
}
}
completions.add("permanent");
completions.addAll(characterIds(args[2]));
if (args[1].equalsIgnoreCase("injure")) completions.add("permanent");
return filter(completions, args[3]);
}

Expand Down Expand Up @@ -358,7 +353,35 @@ private static List<String> characterNames(String playerName) {
return names;
}

/** Slugs are unique within the account; IDs cover characters without a usable slug. */
private static List<String> characterIds(String playerName) {
Player target = Bukkit.getPlayerExact(playerName);
PlayerData data = target == null ? null : PlayerManager.get(target);
if (data == null) return Collections.emptyList();
List<String> ids = new ArrayList<>();
for (RPCharacter character : data.getCharacters()) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (character.getStatus() != Status.ALIVE) continue;
String slug = character.getSlug();
String id = slug != null && !slug.isBlank() && slug.chars().noneMatch(Character::isWhitespace)
&& !slug.equalsIgnoreCase("permanent") ? slug : character.getId();
if (id != null && !id.isBlank()) ids.add(id);
}
return ids;
}

private static boolean canUsePersona(CommandSender sender, String sub) {
if (sub.equals("override")) return sender.hasPermission(Cache.personaOverridePermission);
if (!(sender instanceof Player player)) return false;
return switch (sub) {
case "profile" -> sender.hasPermission(Cache.profilePermission);
case "namecolour" -> PermissionGroupService.canUseNameColour(player);
case "alias", "gender", "description", "birthday" -> sender.hasPermission(Cache.personaSetPermission);
default -> true;
};
}

private List<String> completePersona(CommandSender sender, String sub, String[] args) {
if (!canUsePersona(sender, sub)) return Collections.emptyList();
if (sub.equals("override")) {
return completeOverride(sender, args);
}
Expand All @@ -372,10 +395,6 @@ private List<String> completePersona(CommandSender sender, String sub, String[]
}
return Collections.emptyList();
}
if (!(sender instanceof Player)) {
return Collections.emptyList();
}

switch (sub) {
case "mail":
return args.length == 2 ? filter(List.of("on", "off"), args[1]) : Collections.emptyList();
Expand Down Expand Up @@ -414,9 +433,6 @@ private List<String> completePersona(CommandSender sender, String sub, String[]
}

private List<String> completeOverride(CommandSender sender, String[] args) {
if (!sender.hasPermission(Cache.personaOverridePermission)) {
return Collections.emptyList();
}
if (args.length == 2) {
return Bukkit.getOnlinePlayers().stream()
.map(Player::getName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ public static List<String> tabComplete(Player player, String[] args) {
String name = slot.getDisplayName();
if (name != null && !name.isBlank()) {
String cleaned = name.trim().replace(' ', '_');
if (!cleaned.equalsIgnoreCase(id)
&& !options.contains(cleaned)) {
if (id.equals(resolveSlotArg(snapshot, cleaned))
&& !cleaned.equalsIgnoreCase(id) && !options.contains(cleaned)) {
options.add(cleaned);
}
}
Expand Down
Loading
Loading