diff --git a/api/src/main/java/net/thenextlvl/commander/CommandRegistry.java b/api/src/main/java/net/thenextlvl/commander/CommandRegistry.java index d91a102..bfde54f 100644 --- a/api/src/main/java/net/thenextlvl/commander/CommandRegistry.java +++ b/api/src/main/java/net/thenextlvl/commander/CommandRegistry.java @@ -1,5 +1,7 @@ package net.thenextlvl.commander; +import net.kyori.adventure.audience.Audience; + import java.util.Set; public interface CommandRegistry { @@ -20,4 +22,6 @@ public interface CommandRegistry { boolean unregister(String command); void unregisterCommands(); + + boolean reload(Audience audience); } diff --git a/api/src/main/java/net/thenextlvl/commander/PermissionOverride.java b/api/src/main/java/net/thenextlvl/commander/PermissionOverride.java index 04084f4..8b84adc 100644 --- a/api/src/main/java/net/thenextlvl/commander/PermissionOverride.java +++ b/api/src/main/java/net/thenextlvl/commander/PermissionOverride.java @@ -1,5 +1,6 @@ package net.thenextlvl.commander; +import net.kyori.adventure.audience.Audience; import org.jetbrains.annotations.Nullable; import java.util.Map; @@ -22,4 +23,6 @@ public interface PermissionOverride { boolean reset(String command); void overridePermissions(); + + boolean reload(Audience audience); } diff --git a/api/src/main/resources/commander.properties b/api/src/main/resources/commander.properties index f18ee29..e7c8c7c 100644 --- a/api/src/main/resources/commander.properties +++ b/api/src/main/resources/commander.properties @@ -8,5 +8,9 @@ command.registered= All commands matching All commands matching are now hidden command.revealed= All commands matching can be seen again command.reset= The command has been reset +command.reload.failed= '>Failed to reloaded command files +command.reload.success= Successfully reloaded command files +command.reload.changes= additions and deletions () +command.saved= Successfully saved command files command.unknown= The command () does not exist nothing.changed= Nothing could be changed \ No newline at end of file diff --git a/api/src/main/resources/commander_german.properties b/api/src/main/resources/commander_german.properties index d306e60..e18d990 100644 --- a/api/src/main/resources/commander_german.properties +++ b/api/src/main/resources/commander_german.properties @@ -7,5 +7,9 @@ command.registered= Alle Befehle die glei command.hidden= Alle Befehle die gleichen, wurden versteckt command.revealed= Alle Befehle die gleichen, können wieder gesehen werden command.reset= Der Befehl wurde zurück gesetzt +command.reload.failed= '>Befehlsdateien konnten nicht neu geladen werden +command.reload.success= Befehlsdateien wurden erfolgreich neu geladen +command.reload.changes= Hinzugefügt und entfernt () +command.saved= Befehlsdateien wurden erfolgreich gespeichert command.unknown= Der Befehl () existiert nicht nothing.changed= Es konnte nichts geändert werden \ No newline at end of file diff --git a/paper/src/main/java/net/thenextlvl/commander/paper/command/CommanderCommand.java b/paper/src/main/java/net/thenextlvl/commander/paper/command/CommanderCommand.java index c1cff5d..d965f2c 100644 --- a/paper/src/main/java/net/thenextlvl/commander/paper/command/CommanderCommand.java +++ b/paper/src/main/java/net/thenextlvl/commander/paper/command/CommanderCommand.java @@ -13,8 +13,10 @@ public void register(CommanderPlugin plugin) { .then(new HideCommand(plugin).create()) .then(new PermissionCommand(plugin).create()) .then(new RegisterCommand(plugin).create()) + .then(new ReloadCommand(plugin).create()) .then(new ResetCommand(plugin).create()) .then(new RevealCommand(plugin).create()) + .then(new SaveCommand(plugin).create()) .then(new UnregisterCommand(plugin).create()) .build(); plugin.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> diff --git a/paper/src/main/java/net/thenextlvl/commander/paper/command/ReloadCommand.java b/paper/src/main/java/net/thenextlvl/commander/paper/command/ReloadCommand.java new file mode 100644 index 0000000..eea0d27 --- /dev/null +++ b/paper/src/main/java/net/thenextlvl/commander/paper/command/ReloadCommand.java @@ -0,0 +1,38 @@ +package net.thenextlvl.commander.paper.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.thenextlvl.commander.paper.CommanderPlugin; +import org.bukkit.entity.Player; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +class ReloadCommand { + private final CommanderPlugin plugin; + + public ArgumentBuilder create() { + return Commands.literal("reload").executes(this::reload); + } + + private int reload(CommandContext context) { + var sender = context.getSource().getSender(); + try { + var commands = plugin.commandRegistry().reload(sender); + var permissions = plugin.permissionOverride().reload(sender); + var success = commands || permissions; + if (success && sender instanceof Player player) player.updateCommands(); + var message = success ? "command.reload.success" : "nothing.changed"; + plugin.bundle().sendMessage(sender, message); + return success ? Command.SINGLE_SUCCESS : 0; + } catch (Exception e) { + plugin.bundle().sendMessage(sender, "command.reload.failed", + Placeholder.parsed("error", e.getMessage())); + return 0; + } + } +} diff --git a/paper/src/main/java/net/thenextlvl/commander/paper/command/SaveCommand.java b/paper/src/main/java/net/thenextlvl/commander/paper/command/SaveCommand.java new file mode 100644 index 0000000..4995f7c --- /dev/null +++ b/paper/src/main/java/net/thenextlvl/commander/paper/command/SaveCommand.java @@ -0,0 +1,28 @@ +package net.thenextlvl.commander.paper.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.commander.paper.CommanderPlugin; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +class SaveCommand { + private final CommanderPlugin plugin; + + public ArgumentBuilder create() { + return Commands.literal("save").executes(this::save); + } + + private int save(CommandContext context) { + var sender = context.getSource().getSender(); + plugin.commandRegistry().getHiddenFile().save(); + plugin.commandRegistry().getUnregisteredFile().save(); + plugin.permissionOverride().getOverridesFile().save(); + plugin.bundle().sendMessage(sender, "command.saved"); + return Command.SINGLE_SUCCESS; + } +} diff --git a/paper/src/main/java/net/thenextlvl/commander/paper/implementation/PaperCommandRegistry.java b/paper/src/main/java/net/thenextlvl/commander/paper/implementation/PaperCommandRegistry.java index e593113..d8f4866 100644 --- a/paper/src/main/java/net/thenextlvl/commander/paper/implementation/PaperCommandRegistry.java +++ b/paper/src/main/java/net/thenextlvl/commander/paper/implementation/PaperCommandRegistry.java @@ -5,6 +5,8 @@ import core.file.format.GsonFile; import core.io.IO; import lombok.Getter; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.commander.CommandRegistry; import net.thenextlvl.commander.paper.CommanderPlugin; import org.bukkit.Bukkit; @@ -14,6 +16,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; @Getter public class PaperCommandRegistry implements CommandRegistry { @@ -63,7 +66,7 @@ public boolean isUnregistered(String command) { @Override public boolean register(String command) { - return !plugin.commandFinder().findCommands(new HashSet<>(commands.keySet()).stream(), command).stream() + return !plugin.commandFinder().findCommands(new HashSet<>(unregisteredFile.getRoot()).stream(), command).stream() .filter(unregisteredFile.getRoot()::remove) .filter(this::internalRegister) .toList().isEmpty(); @@ -92,6 +95,56 @@ public void unregisterCommands() { .forEach(this::internalUnregister); } + @Override + public boolean reload(Audience audience) { + var hidden = reloadHidden(audience); + var unregistered = reloadUnregistered(audience); + return hidden || unregistered; + } + + private boolean reloadUnregistered(Audience audience) { + var previous = getUnregisteredFile().getRoot(); + var current = getUnregisteredFile().reload(); + if (previous.equals(current.getRoot())) return false; + var difference = difference(previous, current.getRoot()); + var additions = difference.entrySet().stream() + .filter(Map.Entry::getValue).count(); + plugin.bundle().sendMessage(audience, "command.reload.changes", + Placeholder.parsed("additions", String.valueOf(additions)), + Placeholder.parsed("deletions", String.valueOf(difference.size() - additions)), + Placeholder.parsed("file", "unregistered-commands.json")); + difference.forEach((command, added) -> { + if (added) internalUnregister(command); + else internalRegister(command); + }); + return true; + } + + private boolean reloadHidden(Audience audience) { + var previous = getHiddenFile().getRoot(); + var current = getHiddenFile().reload(); + if (previous.equals(current.getRoot())) return false; + var difference = difference(previous, current.getRoot()); + var additions = difference.entrySet().stream() + .filter(Map.Entry::getValue).count(); + plugin.bundle().sendMessage(audience, "command.reload.changes", + Placeholder.parsed("additions", String.valueOf(additions)), + Placeholder.parsed("deletions", String.valueOf(difference.size() - additions)), + Placeholder.parsed("file", "hidden-commands.json")); + return true; + } + + private Map difference(Set previous, Set current) { + var differences = new HashMap(); + differences.putAll(current.stream() + .filter(s -> !previous.contains(s)) + .collect(Collectors.toMap(s -> s, s -> true))); + differences.putAll(previous.stream() + .filter(s -> !current.contains(s)) + .collect(Collectors.toMap(s -> s, s -> false))); + return differences; + } + private boolean internalRegister(String command) { var register = commands.remove(command); if (register == null) return false; diff --git a/paper/src/main/java/net/thenextlvl/commander/paper/implementation/PaperPermissionOverride.java b/paper/src/main/java/net/thenextlvl/commander/paper/implementation/PaperPermissionOverride.java index 49f3671..567cfb4 100644 --- a/paper/src/main/java/net/thenextlvl/commander/paper/implementation/PaperPermissionOverride.java +++ b/paper/src/main/java/net/thenextlvl/commander/paper/implementation/PaperPermissionOverride.java @@ -6,6 +6,8 @@ import core.io.IO; import lombok.Getter; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.commander.PermissionOverride; import net.thenextlvl.commander.paper.CommanderPlugin; import org.bukkit.Bukkit; @@ -79,6 +81,39 @@ public void overridePermissions() { overridesFile.getRoot().forEach(this::internalOverride); } + @Override + public boolean reload(Audience audience) { + var previous = getOverridesFile().getRoot(); + var current = getOverridesFile().reload(); + if (previous.equals(current.getRoot())) return false; + var difference = difference(previous, current.getRoot()); + var additions = difference.entrySet().stream() + .filter(Map.Entry::getValue).count(); + plugin.bundle().sendMessage(audience, "command.reload.changes", + Placeholder.parsed("additions", String.valueOf(additions)), + Placeholder.parsed("deletions", String.valueOf(difference.size() - additions)), + Placeholder.parsed("file", "permission-overrides.json")); + difference.forEach((command, added) -> { + if (added) override(command.command(), command.permission()); + else reset(command.command()); + }); + return true; + } + + private Map difference(Map previous, Map current) { + var differences = new HashMap(); + current.entrySet().stream() + .filter(entry -> !Objects.equals(previous.get(entry.getKey()), entry.getValue())) + .forEach(entry -> differences.put(new PermissionOverride(entry.getKey(), entry.getValue()), true)); + previous.entrySet().stream() + .filter(entry -> !current.containsKey(entry.getKey())) + .forEach(entry -> differences.put(new PermissionOverride(entry.getKey(), entry.getValue()), false)); + return differences; + } + + private record PermissionOverride(String command, @Nullable String permission) { + } + private boolean internalOverride(String command, @Nullable String permission) { var registered = Bukkit.getCommandMap().getKnownCommands().get(command); if (registered == null) return false; diff --git a/velocity/src/main/java/net/thenextlvl/commander/velocity/command/CommanderCommand.java b/velocity/src/main/java/net/thenextlvl/commander/velocity/command/CommanderCommand.java index 33b7bec..5c9ef8d 100644 --- a/velocity/src/main/java/net/thenextlvl/commander/velocity/command/CommanderCommand.java +++ b/velocity/src/main/java/net/thenextlvl/commander/velocity/command/CommanderCommand.java @@ -11,8 +11,10 @@ public BrigadierCommand create(CommanderPlugin plugin) { .then(new HideCommand(plugin).create()) .then(new PermissionCommand(plugin).create()) .then(new RegisterCommand(plugin).create()) + .then(new ReloadCommand(plugin).create()) .then(new ResetCommand(plugin).create()) .then(new RevealCommand(plugin).create()) + .then(new SaveCommand(plugin).create()) .then(new UnregisterCommand(plugin).create()) .build(); return new BrigadierCommand(command); diff --git a/velocity/src/main/java/net/thenextlvl/commander/velocity/command/ReloadCommand.java b/velocity/src/main/java/net/thenextlvl/commander/velocity/command/ReloadCommand.java new file mode 100644 index 0000000..a8fbd7b --- /dev/null +++ b/velocity/src/main/java/net/thenextlvl/commander/velocity/command/ReloadCommand.java @@ -0,0 +1,35 @@ +package net.thenextlvl.commander.velocity.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.velocitypowered.api.command.BrigadierCommand; +import com.velocitypowered.api.command.CommandSource; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.thenextlvl.commander.velocity.CommanderPlugin; + +@RequiredArgsConstructor +class ReloadCommand { + private final CommanderPlugin plugin; + + public ArgumentBuilder create() { + return BrigadierCommand.literalArgumentBuilder("reload").executes(this::reload); + } + + private int reload(CommandContext context) { + var sender = context.getSource(); + try { + var commands = plugin.commandRegistry().reload(sender); + var permissions = plugin.permissionOverride().reload(sender); + var success = commands || permissions; + var message = success ? "command.reload.success" : "nothing.changed"; + plugin.bundle().sendMessage(sender, message); + return success ? Command.SINGLE_SUCCESS : 0; + } catch (Exception e) { + plugin.bundle().sendMessage(sender, "command.reload.failed", + Placeholder.parsed("error", e.getMessage())); + return 0; + } + } +} diff --git a/velocity/src/main/java/net/thenextlvl/commander/velocity/command/SaveCommand.java b/velocity/src/main/java/net/thenextlvl/commander/velocity/command/SaveCommand.java new file mode 100644 index 0000000..2eb0a76 --- /dev/null +++ b/velocity/src/main/java/net/thenextlvl/commander/velocity/command/SaveCommand.java @@ -0,0 +1,27 @@ +package net.thenextlvl.commander.velocity.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.velocitypowered.api.command.BrigadierCommand; +import com.velocitypowered.api.command.CommandSource; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.commander.velocity.CommanderPlugin; + +@RequiredArgsConstructor +class SaveCommand { + private final CommanderPlugin plugin; + + public ArgumentBuilder create() { + return BrigadierCommand.literalArgumentBuilder("save").executes(this::save); + } + + private int save(CommandContext context) { + var sender = context.getSource(); + plugin.commandRegistry().getHiddenFile().save(); + plugin.commandRegistry().getUnregisteredFile().save(); + plugin.permissionOverride().getOverridesFile().save(); + plugin.bundle().sendMessage(sender, "command.saved"); + return Command.SINGLE_SUCCESS; + } +} diff --git a/velocity/src/main/java/net/thenextlvl/commander/velocity/implementation/ProxyCommandRegistry.java b/velocity/src/main/java/net/thenextlvl/commander/velocity/implementation/ProxyCommandRegistry.java index 3969157..0a95f7f 100644 --- a/velocity/src/main/java/net/thenextlvl/commander/velocity/implementation/ProxyCommandRegistry.java +++ b/velocity/src/main/java/net/thenextlvl/commander/velocity/implementation/ProxyCommandRegistry.java @@ -5,11 +5,16 @@ import core.file.format.GsonFile; import core.io.IO; import lombok.Getter; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.commander.CommandRegistry; import net.thenextlvl.commander.velocity.CommanderPlugin; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; @Getter public class ProxyCommandRegistry implements CommandRegistry { @@ -85,6 +90,55 @@ public void unregisterCommands() { unregisteredCommands().forEach(this::internalUnregister); } + @Override + public boolean reload(Audience audience) { + var hidden = reloadHidden(audience); + var unregistered = reloadUnregistered(audience); + return hidden || unregistered; + } + + private boolean reloadUnregistered(Audience audience) { + var previous = getUnregisteredFile().getRoot(); + var current = getUnregisteredFile().reload(); + if (previous.equals(current.getRoot())) return false; + var difference = difference(previous, current.getRoot()); + var additions = difference.entrySet().stream() + .filter(Map.Entry::getValue).count(); + plugin.bundle().sendMessage(audience, "command.reload.changes", + Placeholder.parsed("additions", String.valueOf(additions)), + Placeholder.parsed("deletions", String.valueOf(difference.size() - additions)), + Placeholder.parsed("file", "unregistered-commands.json")); + difference.forEach((command, added) -> { + if (added) internalUnregister(command); + }); + return true; + } + + private boolean reloadHidden(Audience audience) { + var previous = getHiddenFile().getRoot(); + var current = getHiddenFile().reload(); + if (previous.equals(current.getRoot())) return false; + var difference = difference(previous, current.getRoot()); + var additions = difference.entrySet().stream() + .filter(Map.Entry::getValue).count(); + plugin.bundle().sendMessage(audience, "command.reload.changes", + Placeholder.parsed("additions", String.valueOf(additions)), + Placeholder.parsed("deletions", String.valueOf(difference.size() - additions)), + Placeholder.parsed("file", "hidden-commands.json")); + return true; + } + + private Map difference(Set previous, Set current) { + var differences = new HashMap(); + differences.putAll(current.stream() + .filter(s -> !previous.contains(s)) + .collect(Collectors.toMap(s -> s, s -> true))); + differences.putAll(previous.stream() + .filter(s -> !current.contains(s)) + .collect(Collectors.toMap(s -> s, s -> false))); + return differences; + } + private boolean internalUnregister(String command) { plugin.server().getCommandManager().unregister(command); return true; diff --git a/velocity/src/main/java/net/thenextlvl/commander/velocity/implementation/ProxyPermissionOverride.java b/velocity/src/main/java/net/thenextlvl/commander/velocity/implementation/ProxyPermissionOverride.java index f371a35..50cf878 100644 --- a/velocity/src/main/java/net/thenextlvl/commander/velocity/implementation/ProxyPermissionOverride.java +++ b/velocity/src/main/java/net/thenextlvl/commander/velocity/implementation/ProxyPermissionOverride.java @@ -6,6 +6,8 @@ import core.io.IO; import lombok.Getter; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.commander.PermissionOverride; import net.thenextlvl.commander.velocity.CommanderPlugin; import org.jetbrains.annotations.Nullable; @@ -78,4 +80,37 @@ public boolean reset(String command) { public void overridePermissions() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } + + @Override + public boolean reload(Audience audience) { + var previous = getOverridesFile().getRoot(); + var current = getOverridesFile().reload(); + if (previous.equals(current.getRoot())) return false; + var difference = difference(previous, current.getRoot()); + var additions = difference.entrySet().stream() + .filter(Map.Entry::getValue).count(); + plugin.bundle().sendMessage(audience, "command.reload.changes", + Placeholder.parsed("additions", String.valueOf(additions)), + Placeholder.parsed("deletions", String.valueOf(difference.size() - additions)), + Placeholder.parsed("file", "permission-overrides.json")); + difference.forEach((command, added) -> { + if (added) override(command.command(), command.permission()); + else reset(command.command()); + }); + return true; + } + + private Map difference(Map previous, Map current) { + var differences = new HashMap(); + current.entrySet().stream() + .filter(entry -> !Objects.equals(previous.get(entry.getKey()), entry.getValue())) + .forEach(entry -> differences.put(new PermissionOverride(entry.getKey(), entry.getValue()), true)); + previous.entrySet().stream() + .filter(entry -> !current.containsKey(entry.getKey())) + .forEach(entry -> differences.put(new PermissionOverride(entry.getKey(), entry.getValue()), false)); + return differences; + } + + private record PermissionOverride(String command, @Nullable String permission) { + } }