From aeb2404784f289d968fde4d00fec837b500a9ee9 Mon Sep 17 00:00:00 2001 From: Yann Date: Fri, 10 May 2024 20:16:57 +0200 Subject: [PATCH] feat: centralize command & more use of api instead of common Start process to centralize command - Not implemented yet Use more of api to remove class in common especially for ServerPlayer --- .../aurionchat/api/model/ServerPlayer.java | 9 ++ build.gradle | 3 + bukkit/build.gradle | 4 +- .../aurionchat/bukkit/AurionChat.java | 76 +++++----- .../aurionchat/bukkit/Bootstrap.java | 30 ++++ .../bukkit/BukkitConfigurationAdapter.java | 4 +- .../aurionchat/bukkit/CommandExecutor.java | 86 +++++++++++ .../aurionchat/bukkit/PlayerFactory.java | 37 ----- .../aurionchat/bukkit/PluginLoader.java | 22 --- .../aurionchat/bukkit/SenderFactory.java | 59 ++++++++ .../bukkit/command/ChatCommand.java | 45 ------ .../bukkit/listeners/CommandListener.java | 2 +- .../bukkit/listeners/LoginListener.java | 6 +- bukkit/src/main/resources/plugin.yml | 12 +- common/build.gradle | 2 + .../aurionchat/common/AurionChatPlayer.java | 3 +- .../aurionchat/common/AurionChatPlugin.java | 13 -- .../aurionchat/common/ChatService.java | 7 +- .../common/command/AbstractCommand.java | 93 ++++++++++++ .../aurionchat/common/command/Argument.java | 23 +++ .../command/BrigadierCommandManager.java | 69 +++++++++ .../common/command/CommandManager.java | 120 ++++++++++++++++ .../common/command/CommandSpec.java | 76 ++++++++++ .../common/command/SingleCommand.java | 48 +++++++ .../common/command/sender/AbstractSender.java | 135 ++++++++++++++++++ .../command/sender/DummyConsoleSender.java | 30 ++++ .../common/command/sender/SenderFactory.java | 51 +++++++ .../tabcomplete/CompletionSupplier.java | 16 +++ .../command/tabcomplete/TabCompleter.java | 71 +++++++++ .../ChatCommandCommon.java | 4 +- .../common/listeners/LoginListenerCommon.java | 2 +- .../aurionchat/common/locale/Message.java | 123 ++++++++++++++++ .../common/misc/ArgumentTokenizer.java | 22 +++ .../common/misc/ImmutableCollectors.java | 19 +++ .../aurionchat/common/misc/Predicates.java | 50 +++++++ .../common/misc/QuotedStringTokenizer.java | 91 ++++++++++++ .../{ => plugin}/AbstractAurionChat.java | 47 +++--- .../common/plugin/AurionChatBootstrap.java | 22 +++ .../common/plugin/AurionChatPlugin.java | 20 +++ .../src/test/java/TestMessageProcessing.java | 2 +- .../aurionchat/fabric/AurionChat.java | 66 ++++----- .../aurionchat/fabric/Bootstrap.java | 54 +++++++ .../aurionchat/fabric/CommandExecutor.java | 43 ++++++ .../aurionchat/fabric/PlayerFactory.java | 30 ---- .../aurionchat/fabric/SenderFactory.java | 60 ++++++++ .../fabric/command/ChatCommand.java | 99 ------------- .../fabric/listeners/ChatListener.java | 2 +- .../fabric/listeners/LoginListener.java | 4 +- .../mixin/ServerCommandSourceAccessor.java | 14 ++ .../src/main/resources/aurionchat.mixins.json | 13 ++ fabric/src/main/resources/fabric.mod.json | 5 +- .../aurionchat/forge/AurionChat.java | 92 ++++-------- .../aurionchat/forge/Bootstrap.java | 95 ++++++++++++ .../aurionchat/forge/CommandExecutor.java | 40 ++++++ .../aurionchat/forge/PlayerFactory.java | 30 ---- .../aurionchat/forge/SenderFactory.java | 63 ++++++++ .../aurionchat/forge/command/ChatCommand.java | 103 ------------- .../forge/listeners/LoginListener.java | 5 +- .../aurionchat/sponge/AurionChat.java | 108 ++++++-------- .../aurionchat/sponge/Bootstrap.java | 97 +++++++++++++ .../aurionchat/sponge/CommandExecutor.java | 67 +++++++++ .../aurionchat/sponge/PlayerFactory.java | 34 ----- .../aurionchat/sponge/SenderFactory.java | 60 ++++++++ .../sponge/command/ChatCommand.java | 92 ------------ .../sponge/listeners/ChatListener.java | 2 +- .../sponge/listeners/LoginListener.java | 6 +- 66 files changed, 2062 insertions(+), 776 deletions(-) create mode 100644 bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/Bootstrap.java create mode 100644 bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/CommandExecutor.java delete mode 100644 bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/PlayerFactory.java delete mode 100644 bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/PluginLoader.java create mode 100644 bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/SenderFactory.java delete mode 100644 bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/command/ChatCommand.java delete mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/AurionChatPlugin.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/command/AbstractCommand.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/command/Argument.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/command/BrigadierCommandManager.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/command/CommandManager.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/command/CommandSpec.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/command/SingleCommand.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/command/sender/AbstractSender.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/command/sender/DummyConsoleSender.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/command/sender/SenderFactory.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/command/tabcomplete/CompletionSupplier.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/command/tabcomplete/TabCompleter.java rename common/src/main/java/com/mineaurion/aurionchat/common/{command => commands}/ChatCommandCommon.java (95%) create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/locale/Message.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/misc/ArgumentTokenizer.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/misc/ImmutableCollectors.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/misc/Predicates.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/misc/QuotedStringTokenizer.java rename common/src/main/java/com/mineaurion/aurionchat/common/{ => plugin}/AbstractAurionChat.java (62%) create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/plugin/AurionChatBootstrap.java create mode 100644 common/src/main/java/com/mineaurion/aurionchat/common/plugin/AurionChatPlugin.java create mode 100644 fabric/src/main/java/com/mineaurion/aurionchat/fabric/Bootstrap.java create mode 100644 fabric/src/main/java/com/mineaurion/aurionchat/fabric/CommandExecutor.java delete mode 100644 fabric/src/main/java/com/mineaurion/aurionchat/fabric/PlayerFactory.java create mode 100644 fabric/src/main/java/com/mineaurion/aurionchat/fabric/SenderFactory.java delete mode 100644 fabric/src/main/java/com/mineaurion/aurionchat/fabric/command/ChatCommand.java create mode 100644 fabric/src/main/java/com/mineaurion/aurionchat/fabric/mixin/ServerCommandSourceAccessor.java create mode 100644 fabric/src/main/resources/aurionchat.mixins.json create mode 100644 forge/src/main/java/com/mineaurion/aurionchat/forge/Bootstrap.java create mode 100644 forge/src/main/java/com/mineaurion/aurionchat/forge/CommandExecutor.java delete mode 100644 forge/src/main/java/com/mineaurion/aurionchat/forge/PlayerFactory.java create mode 100644 forge/src/main/java/com/mineaurion/aurionchat/forge/SenderFactory.java delete mode 100644 forge/src/main/java/com/mineaurion/aurionchat/forge/command/ChatCommand.java create mode 100644 sponge/src/main/java/com/mineaurion/aurionchat/sponge/Bootstrap.java create mode 100644 sponge/src/main/java/com/mineaurion/aurionchat/sponge/CommandExecutor.java delete mode 100644 sponge/src/main/java/com/mineaurion/aurionchat/sponge/PlayerFactory.java create mode 100644 sponge/src/main/java/com/mineaurion/aurionchat/sponge/SenderFactory.java delete mode 100644 sponge/src/main/java/com/mineaurion/aurionchat/sponge/command/ChatCommand.java diff --git a/api/src/main/java/com/mineaurion/aurionchat/api/model/ServerPlayer.java b/api/src/main/java/com/mineaurion/aurionchat/api/model/ServerPlayer.java index 5cd33bd..13a84e7 100644 --- a/api/src/main/java/com/mineaurion/aurionchat/api/model/ServerPlayer.java +++ b/api/src/main/java/com/mineaurion/aurionchat/api/model/ServerPlayer.java @@ -2,8 +2,17 @@ import net.kyori.adventure.text.Component; +import java.util.UUID; + public interface ServerPlayer extends Player { + + UUID CONSOLE_UUID = new UUID(0,0); + + String CONSOLE_NAME = "Console"; + void sendMessage(Component message); boolean hasPermission(String permission); + + boolean isConsole(); } diff --git a/build.gradle b/build.gradle index bb68177..e6daf36 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,9 @@ subprojects { repositories { mavenCentral() + maven { + url "https://libraries.minecraft.net" + } } java { diff --git a/bukkit/build.gradle b/bukkit/build.gradle index 75dfe25..528bef7 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -7,12 +7,14 @@ repositories { name = 'sonatype' url = 'https://oss.sonatype.org/content/groups/public/' } + maven { url 'https://repo.papermc.io/repository/maven-public/' } + } dependencies { implementation project(':common') - compileOnly 'org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT' + compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT' api 'net.kyori:adventure-platform-bukkit:4.3.0' //compile files('libs/spigot-1.7.10-SNAPSHOT-b1657.jar') } diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/AurionChat.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/AurionChat.java index dd4d7da..e6c5d89 100644 --- a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/AurionChat.java +++ b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/AurionChat.java @@ -1,86 +1,88 @@ package com.mineaurion.aurionchat.bukkit; -import com.mineaurion.aurionchat.bukkit.command.ChatCommand; import com.mineaurion.aurionchat.bukkit.listeners.ChatListener; import com.mineaurion.aurionchat.bukkit.listeners.CommandListener; import com.mineaurion.aurionchat.bukkit.listeners.LoginListener; -import com.mineaurion.aurionchat.common.AbstractAurionChat; import com.mineaurion.aurionchat.common.config.ConfigurationAdapter; import com.mineaurion.aurionchat.common.logger.JavaPluginLogger; import com.mineaurion.aurionchat.common.logger.PluginLogger; +import com.mineaurion.aurionchat.common.plugin.AbstractAurionChat; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.command.PluginCommand; import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.java.JavaPlugin; - -import java.nio.file.Path; public class AurionChat extends AbstractAurionChat { - public final JavaPlugin plugin; + public final Bootstrap bootstrap; public BukkitAudiences audiences; - private PlayerFactory playerFactory; + public SenderFactory senderFactory; - public AurionChat(JavaPlugin plugin){ - this.plugin = plugin; - } + private CommandExecutor commandManager; - public void onEnable(){ - getlogger().info("AurionChat Initializing"); - audiences = BukkitAudiences.create(plugin); - this.enable(); + public AurionChat(Bootstrap bootstrap){ + super(new JavaPluginLogger(Bukkit.getLogger())); + this.bootstrap = bootstrap; } - public void onDisable() { - this.disable(); + @Override + protected void registerPlatformListeners() { + PluginManager pluginManager = getServer().getPluginManager(); + pluginManager.registerEvents(new LoginListener(this), getBootstrap()); + pluginManager.registerEvents(new CommandListener(this), getBootstrap()); + pluginManager.registerEvents(new ChatListener(this), getBootstrap()); } @Override - protected void registerPlatformListeners() { - PluginManager pluginManager = plugin.getServer().getPluginManager(); - pluginManager.registerEvents(new LoginListener(this), plugin); - pluginManager.registerEvents(new CommandListener(this), plugin); - pluginManager.registerEvents(new ChatListener(this), plugin); + protected void setupSenderFactory() { + this.audiences = BukkitAudiences.create(getBootstrap()); + this.senderFactory = new SenderFactory(this); } @Override public ConfigurationAdapter getConfigurationAdapter() { - return new BukkitConfigurationAdapter(this, resolveConfig("config.yml").toFile()); + return new BukkitConfigurationAdapter(resolveConfig("config.yml").toFile()); + } + + public SenderFactory getSenderFactory(){ + return this.senderFactory; } @Override - protected Path getConfigDirectory() { - return this.plugin.getDataFolder().toPath().toAbsolutePath(); + public Bootstrap getBootstrap(){ + return this.bootstrap; + } + + public Server getServer(){ + return bootstrap.getServer(); } @Override protected void registerCommands() { - plugin.getCommand("chat").setExecutor(new ChatCommand(this)); + PluginCommand command = bootstrap.getCommand("chat"); + if(command == null){ + getLogger().severe("Unable to register /chat command with the server"); + return; + } + + this.commandManager = new CommandExecutor(this, command); + this.commandManager.register(); } @Override protected void disablePlugin() { - plugin.getServer().getPluginManager().disablePlugin(plugin); + getServer().getPluginManager().disablePlugin(getBootstrap()); } @Override - public PluginLogger getlogger() { + public PluginLogger getLogger() { return new JavaPluginLogger(Bukkit.getLogger()); } public BukkitAudiences getAudiences(){ return audiences; } - - @Override - protected void setupPlayerFactory() { - this.playerFactory = new PlayerFactory(this); - } - - @Override - public PlayerFactory getPlayerFactory() { - return playerFactory; - } } \ No newline at end of file diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/Bootstrap.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/Bootstrap.java new file mode 100644 index 0000000..abbaab5 --- /dev/null +++ b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/Bootstrap.java @@ -0,0 +1,30 @@ +package com.mineaurion.aurionchat.bukkit; + +import com.mineaurion.aurionchat.common.plugin.AurionChatBootstrap; +import org.bukkit.plugin.java.JavaPlugin; + +import java.nio.file.Path; + +public class Bootstrap extends JavaPlugin implements AurionChatBootstrap { + private final AurionChat plugin; + + + public Bootstrap(){ + this.plugin = new AurionChat(this); + } + + @Override + public void onEnable(){ + plugin.enable(); + } + + @Override + public void onDisable() { + plugin.disable(); + } + + @Override + public Path getConfigDirectory() { + return getDataFolder().toPath().toAbsolutePath(); + } +} diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/BukkitConfigurationAdapter.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/BukkitConfigurationAdapter.java index ed00f5f..9193d1b 100644 --- a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/BukkitConfigurationAdapter.java +++ b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/BukkitConfigurationAdapter.java @@ -11,12 +11,10 @@ public class BukkitConfigurationAdapter implements ConfigurationAdapter { - private final AurionChat plugin; private final File file; private YamlConfiguration configuration; - public BukkitConfigurationAdapter(AurionChat plugin, File file){ - this.plugin = plugin; + public BukkitConfigurationAdapter(File file){ this.file = file; reload(); } diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/CommandExecutor.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/CommandExecutor.java new file mode 100644 index 0000000..e6cab90 --- /dev/null +++ b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/CommandExecutor.java @@ -0,0 +1,86 @@ +package com.mineaurion.aurionchat.bukkit; + +import com.mineaurion.aurionchat.common.command.CommandManager; +import org.bukkit.Server; +import org.bukkit.command.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.ServerCommandEvent; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; + + +public class CommandExecutor extends CommandManager implements TabExecutor, Listener { + + private static final boolean SELECT_ENTITIES_SUPPORTED; + + static { + boolean selectEntitesSupported = false; + try { + Server.class.getMethod("selectEntities", CommandSender.class, String.class); + selectEntitesSupported = true; + } catch (NoSuchMethodException e){ + // ignore + } + SELECT_ENTITIES_SUPPORTED = selectEntitesSupported; + } + + protected final AurionChat plugin; + protected final PluginCommand command; + + public CommandExecutor(AurionChat plugin, PluginCommand command){ + super(plugin); + this.plugin = plugin; + this.command = command; + } + + public void register(){ + this.command.setExecutor(this); + this.plugin.getServer().getPluginManager().registerEvents(this, this.plugin.bootstrap); + } + + @Override + public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, @NonNull String[] args) { + executeCommand( + this.plugin.getSenderFactory().wrap(sender), + label, + Arrays.asList(args) + ); + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + return tabCompleteCommand(plugin.getSenderFactory().wrap(sender), Arrays.asList(args)); + } + + // Support AurionChat command prefixed with a '/' from the console + @EventHandler(ignoreCancelled = true) + public void onConsoleCommand(ServerCommandEvent e){ + if(!(e.getSender() instanceof ConsoleCommandSender)){ + return; + } + + String buffer = e.getCommand(); + if(buffer.isEmpty() || buffer.charAt(0) != '/'){ + return; + } + + buffer = buffer.substring(1); + + String commandLabel; + int firstSpace = buffer.indexOf(' '); + commandLabel = firstSpace == -1 ? buffer : buffer.substring(0, firstSpace); + Command command = this.plugin.getBootstrap().getServer().getCommandMap().getCommand(commandLabel); + if(command != this.command){ + return; + } + + e.setCommand(buffer); + } + +} \ No newline at end of file diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/PlayerFactory.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/PlayerFactory.java deleted file mode 100644 index 67e23af..0000000 --- a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/PlayerFactory.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.mineaurion.aurionchat.bukkit; - -import net.kyori.adventure.platform.bukkit.BukkitAudiences; -import net.kyori.adventure.text.Component; -import org.bukkit.entity.Player; - -import java.util.UUID; - -public class PlayerFactory extends com.mineaurion.aurionchat.common.player.PlayerFactory { - - private final BukkitAudiences audiences; - - public PlayerFactory(AurionChat plugin){ - super(true); - this.audiences = plugin.getAudiences(); - } - - @Override - protected UUID getUUID(Player player) { - return player.getUniqueId(); - } - - @Override - protected String getName(Player player) { - return player.getName(); - } - - @Override - protected void sendMessage(Player player, Component message) { - this.audiences.player(player).sendMessage(message); - } - - @Override - protected boolean hasPermission(Player player, String permission) { - return player.hasPermission(permission); - } -} diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/PluginLoader.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/PluginLoader.java deleted file mode 100644 index 4e1bb71..0000000 --- a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/PluginLoader.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.mineaurion.aurionchat.bukkit; - -import org.bukkit.plugin.java.JavaPlugin; - -public class PluginLoader extends JavaPlugin { - - private final AurionChat plugin; - - public PluginLoader(){ - plugin = new AurionChat(this); - } - - @Override - public void onEnable(){ - plugin.onEnable(); - } - - @Override - public void onDisable(){ - plugin.onDisable(); - } -} diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/SenderFactory.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/SenderFactory.java new file mode 100644 index 0000000..8039f32 --- /dev/null +++ b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/SenderFactory.java @@ -0,0 +1,59 @@ +package com.mineaurion.aurionchat.bukkit; + + +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class SenderFactory extends com.mineaurion.aurionchat.common.command.sender.SenderFactory { + + private final BukkitAudiences audiences; + + public SenderFactory(AurionChat plugin){ + super(plugin, true); + this.audiences = plugin.getAudiences(); + } + + @Override + protected String getName(CommandSender sender) { + return sender instanceof Player ? sender.getName() : ServerPlayer.CONSOLE_NAME; + } + + // TODO: need to check this + @Override + protected String getDisplayName(CommandSender sender) { + return sender.getName(); + } + + @Override + protected UUID getId(CommandSender sender) { + return sender instanceof Player ? ((Player) sender).getUniqueId() : ServerPlayer.CONSOLE_UUID; + } + + @Override + protected void sendMessage(CommandSender sender, Component message) { + this.audiences.sender(sender).sendMessage(message); + } + + @Override + protected boolean hasPermission(CommandSender sender, String permission) { + return sender.hasPermission(permission); + } + + @Override + protected boolean isConsole(CommandSender sender) { + return sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender; + } + + @Override + public void close() { + super.close(); + this.audiences.close(); + } +} \ No newline at end of file diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/command/ChatCommand.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/command/ChatCommand.java deleted file mode 100644 index f238bc9..0000000 --- a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/command/ChatCommand.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.mineaurion.aurionchat.bukkit.command; - -import com.mineaurion.aurionchat.bukkit.AurionChat; -import com.mineaurion.aurionchat.common.AurionChatPlayer; -import com.mineaurion.aurionchat.common.command.ChatCommandCommon; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -public class ChatCommand extends ChatCommandCommon implements CommandExecutor { - - public ChatCommand(AurionChat plugin) { - super(plugin); - } - - @Override - public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){ - if(cmd.getName().equalsIgnoreCase("chat") || cmd.getName().equalsIgnoreCase("ch")){ - if(!(sender instanceof Player)){ - return false; - } - AurionChatPlayer aurionChatPlayer = getPlugin().getAurionChatPlayers().get(Bukkit.getPlayer(sender.getName()).getUniqueId()); - String command = args.length < 1 ? "DEFAULT" : args[0]; - String channel = (args.length == 2) ? args[1] : ""; - - try { - Action action; - if(channel.equalsIgnoreCase("alllisten")){ - action = Action.ALLLISTEN; - } else { - action = Action.valueOf(command.toUpperCase()); - } - return this.execute(aurionChatPlayer, channel, action); - } catch (IllegalArgumentException e){ - aurionChatPlayer.sendMessage(Component.text("This command doesn't exist").color(NamedTextColor.GOLD)); - return false; - } - } - return false; - } -} diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/CommandListener.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/CommandListener.java index 449e9f4..e153201 100644 --- a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/CommandListener.java +++ b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/CommandListener.java @@ -1,7 +1,7 @@ package com.mineaurion.aurionchat.bukkit.listeners; import com.mineaurion.aurionchat.bukkit.AurionChat; -import com.mineaurion.aurionchat.common.command.ChatCommandCommon; +import com.mineaurion.aurionchat.common.commands.ChatCommandCommon; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.apache.commons.lang.ArrayUtils; diff --git a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/LoginListener.java b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/LoginListener.java index 17d43f3..3aec098 100644 --- a/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/LoginListener.java +++ b/bukkit/src/main/java/com/mineaurion/aurionchat/bukkit/listeners/LoginListener.java @@ -17,16 +17,16 @@ public LoginListener(AurionChat plugin){ @EventHandler(priority = EventPriority.LOW) public void onPlayerKick(PlayerKickEvent event){ - playerLeaving(plugin.getPlayerFactory().wrap(event.getPlayer())); + playerLeaving(plugin.getSenderFactory().wrap(event.getPlayer())); } @EventHandler(priority = EventPriority.LOW) public void onPlayerQuit(PlayerQuitEvent event){ - playerLeaving(plugin.getPlayerFactory().wrap(event.getPlayer())); + playerLeaving(plugin.getSenderFactory().wrap(event.getPlayer())); } @EventHandler(priority = EventPriority.LOW) public void onPlayerJoin(PlayerJoinEvent event){ - playerJoin(plugin.getPlayerFactory().wrap(event.getPlayer())); + playerJoin(plugin.getSenderFactory().wrap(event.getPlayer())); } } diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml index ff07546..09aa0b1 100644 --- a/bukkit/src/main/resources/plugin.yml +++ b/bukkit/src/main/resources/plugin.yml @@ -1,21 +1,15 @@ name: Aurionchat version: ${version} -main: com.mineaurion.aurionchat.bukkit.PluginLoader +main: com.mineaurion.aurionchat.bukkit.Bootstrap authors: [Yann151924] website: https://mineaurion.com depend: - LuckPerms + commands: chat: - aliases: [ch, channel] + aliases: [ch, channel, aurionchat] description: "AurionChat's command to manage chat channels" - usage: | - / - displays player current status on aurionchat - / join - join the specified channel and set as current one - / leave - leave the specified channel - / spy - spy/listen the channel speicified - / allListen - listen to all channels - / reload - reload the rabbitmq connection permissions: aurionchat.chat.colors: diff --git a/common/build.gradle b/common/build.gradle index 527f083..b02eae0 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -17,6 +17,8 @@ dependencies { compileOnly 'com.google.guava:guava:33.1.0-jre' compileOnly 'org.jetbrains:annotations:21.+' + compileOnly 'com.mojang:brigadier:1.0.18' + api project(':api') api('org.spongepowered:configurate-core:4.1.2') diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/AurionChatPlayer.java b/common/src/main/java/com/mineaurion/aurionchat/common/AurionChatPlayer.java index 0d8e871..ff56b0f 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/AurionChatPlayer.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/AurionChatPlayer.java @@ -3,6 +3,7 @@ import com.mineaurion.aurionchat.api.AurionPlayer; import com.mineaurion.aurionchat.api.model.Player; import com.mineaurion.aurionchat.api.model.ServerPlayer; +import com.mineaurion.aurionchat.common.plugin.AbstractAurionChat; import net.kyori.adventure.text.Component; import java.util.Collections; @@ -17,7 +18,7 @@ public class AurionChatPlayer extends AurionPlayer { public AurionChatPlayer(ServerPlayer player, AbstractAurionChat plugin){ super(player.getId(), - player.getDisplayName(), + player.getName(), player.getPrefix(), player.getSuffix()); this.player = player; diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/AurionChatPlugin.java b/common/src/main/java/com/mineaurion/aurionchat/common/AurionChatPlugin.java deleted file mode 100644 index 7b57486..0000000 --- a/common/src/main/java/com/mineaurion/aurionchat/common/AurionChatPlugin.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.mineaurion.aurionchat.common; - -import com.mineaurion.aurionchat.common.logger.PluginLogger; - -public interface AurionChatPlugin { - - /** - * Gets a wrapped logger instance for the platform. - * - * @return the plugin's logger - */ - PluginLogger getlogger(); -} diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/ChatService.java b/common/src/main/java/com/mineaurion/aurionchat/common/ChatService.java index 2bf97c8..91c007e 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/ChatService.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/ChatService.java @@ -2,6 +2,7 @@ import com.mineaurion.aurionchat.api.AurionPacket; import com.mineaurion.aurionchat.common.config.ConfigurationAdapter; +import com.mineaurion.aurionchat.common.plugin.AbstractAurionChat; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; @@ -21,7 +22,7 @@ public class ChatService { private Connection connection; private Channel channel; - protected boolean connected = false; + public boolean connected = false; private final AbstractAurionChat plugin; private final ConfigurationAdapter config; @@ -80,7 +81,7 @@ private DeliverCallback consumer(){ AurionPacket packet = AurionPacket.fromJson(new String(delivery.getBody(), StandardCharsets.UTF_8)); Component messageDeserialize = packet.getComponent(); if(this.config.getBoolean("options.spy", false)){ - plugin.getlogger().info(packet.getDisplayString()); + plugin.getLogger().info(packet.getDisplayString()); } plugin.getAurionChatPlayers().forEach((uuid, aurionChatPlayers) -> { @@ -93,7 +94,7 @@ private DeliverCallback consumer(){ aurionChatPlayers.sendMessage(messageDeserialize); } } else { - plugin.getlogger().warn("Received message with the type " + packet.getType() + " and the message was " + packet + ". It won't be processed"); + plugin.getLogger().warn("Received message with the type " + packet.getType() + " and the message was " + packet + ". It won't be processed"); } }); }; diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/AbstractCommand.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/AbstractCommand.java new file mode 100644 index 0000000..d345b41 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/AbstractCommand.java @@ -0,0 +1,93 @@ +package com.mineaurion.aurionchat.common.command; + +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +public abstract class AbstractCommand { + + private final @NonNull CommandSpec spec; + private final @NonNull String name; + + private final @NonNull String permission; + + private final @Nullable String adminPermission; + + private final @NonNull Predicate argumentCheck; + + public AbstractCommand(@NonNull CommandSpec spec, @NonNull String name, @NonNull String permission, @NonNull Predicate argumentCheck){ + this( + spec, + name, + permission, + null, + argumentCheck + ); + } + + public AbstractCommand(@NonNull CommandSpec spec, @NonNull String name, @NonNull String permission, @Nullable String adminPermission, @NonNull Predicate argumentCheck){ + this.spec = spec; + this.name = name; + this.permission = permission; + this.adminPermission = adminPermission; + this.argumentCheck = argumentCheck; + } + + // Main execution method for the command. + public abstract void execute(AurionChatPlugin plugin, ServerPlayer sender, List args, String label) throws Exception; + + + // Tab completion method - default implementation provided since some commands do not provide more tab completion. + public List tabComplete(AurionChatPlugin plugin, ServerPlayer sender, List args) { + return Collections.emptyList(); + } + + public @NonNull CommandSpec getSpec(){ + return this.spec; + } + + public @NonNull String getName() { + return this.name; + } + + public String getUsage(){ + String usage = getSpec().usage(); + return usage == null ? "" : usage; + } + + public @NotNull String getPermission(){ + return permission; + } + + public Optional getAdminPermission(){ + return Optional.ofNullable(adminPermission); + } + + public Optional> getArgs(){ + return Optional.ofNullable(getSpec().args()); + } + + public @NonNull Predicate getArgumentCheck(){ + return this.argumentCheck; + } + + /** + * The admin permission in untreated here, directly handle in the command class for less complication + * @return boolean true if the sender has permissions + */ + public boolean isAuthorized(ServerPlayer sender){ + return sender.hasPermission(getPermission()); + } + + /** + * Sends a brief command usage message to the Sender. + */ + public abstract void sendUsage(ServerPlayer sender, String label); +} diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/Argument.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/Argument.java new file mode 100644 index 0000000..9ad1739 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/Argument.java @@ -0,0 +1,23 @@ +package com.mineaurion.aurionchat.common.command; + +import com.mineaurion.aurionchat.common.locale.Message; +import net.kyori.adventure.text.Component; + +public class Argument { + + private final String name; + private final boolean required; + + public Argument(String name, boolean required){ + this.name = name; + this.required = required; + } + + public String getName(){ + return this.name; + } + + public Component asComponent(){ + return (this.required ? Message.REQUIRED_ARGUMENT : Message.OPTIONAL_ARGUMENT).build(this.name); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/BrigadierCommandManager.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/BrigadierCommandManager.java new file mode 100644 index 0000000..0c40863 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/BrigadierCommandManager.java @@ -0,0 +1,69 @@ +package com.mineaurion.aurionchat.common.command; + +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import com.mineaurion.aurionchat.common.misc.ArgumentTokenizer; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public abstract class BrigadierCommandManager extends CommandManager implements Command, SuggestionProvider { + + protected static final String[] COMMAND_ALIASES = new String[]{"aurionchat", "chat", "c"}; + + protected BrigadierCommandManager(AurionChatPlugin plugin){ + super(plugin); + } + + public abstract ServerPlayer getSender(S source); + + @Override + public int run(CommandContext context) throws CommandSyntaxException { + S source = context.getSource(); + ServerPlayer sender = getSender(source); + + int start = context.getRange().getStart(); + String buffer = context.getInput().substring(start); + + List arguments = ArgumentTokenizer.EXECUTE.tokenizeInput(buffer); + + String label = arguments.remove(0); + if(label.startsWith("/")){ + label = label.substring(1); + } + + executeCommand(sender, label, arguments); + return Command.SINGLE_SUCCESS; + } + + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + S source = context.getSource(); + ServerPlayer sender = getSender(source); + + int idx = builder.getStart(); + + String buffer = builder.getInput().substring(idx); + idx += buffer.length(); + + List arguments = ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(buffer); + + if(!arguments.isEmpty()){ + idx -= arguments.get(arguments.size() - 1).length(); + } + + List completions = tabCompleteCommand(sender, arguments); + + builder = builder.createOffset(idx); + for(String completion: completions){ + builder.suggest(completion); + } + return builder.buildFuture(); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/CommandManager.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/CommandManager.java new file mode 100644 index 0000000..ef2d285 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/CommandManager.java @@ -0,0 +1,120 @@ +package com.mineaurion.aurionchat.common.command; + + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import com.mineaurion.aurionchat.common.command.tabcomplete.CompletionSupplier; +import com.mineaurion.aurionchat.common.command.tabcomplete.TabCompleter; +import com.mineaurion.aurionchat.common.locale.Message; +import com.mineaurion.aurionchat.common.misc.ImmutableCollectors; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class CommandManager { + + private final AurionChatPlugin plugin; + private final Map mainCommands; + + public CommandManager(AurionChatPlugin plugin){ + this.plugin = plugin; + this.mainCommands = ImmutableList.builder() +// .add(new BalanceInfo()) +// .add(new SetAmount()) +// .add(new AddAmount()) +// .add(new WithdrawAmount()) +// .add(new Pay()) + .build() + .stream() + .collect(ImmutableCollectors.toMap(c -> c.getName().toLowerCase(Locale.ROOT), Function.identity())); + } + + public AurionChatPlugin getPlugin(){ + return this.plugin; + } + + @VisibleForTesting + public Map getMainCommands(){ + return this.mainCommands; + } + + public void executeCommand(ServerPlayer sender, String label, List args){ + UUID uuid = sender.getId(); + + try { + execute(sender, label, args); + } catch (Throwable e){ + this.plugin.getLogger().severe("Exception whilst executing command: " + args, e); + } + } + + private void execute(ServerPlayer sender, String label, List arguments){ + if(arguments.isEmpty() || arguments.size() == 1 && arguments.get(0).trim().isEmpty()){ + sender.sendMessage(Message.prefixed(Component.text() + .color(NamedTextColor.DARK_GREEN) + .append(Component.text("Running Aurionchat")) + .append(Component.space()) + )); + return; + } + + AbstractCommand main = this.mainCommands.get(arguments.get(0).toLowerCase(Locale.ROOT)); + + if(main == null){ + sendCommandUsage(sender, label); + return; + } + + if(main.getArgumentCheck().test(arguments.size())){ + main.sendUsage(sender, label); + return; + } + + //Exec command + try{ + main.execute(this.plugin, sender, arguments, label); + } catch (Exception e){ + e.printStackTrace(); + } + } + + public List tabCompleteCommand(ServerPlayer sender, List arguments){ + final List mains = this.mainCommands.values().stream() + .filter(m -> m.isAuthorized(sender)) + .collect(Collectors.toList()); + + return TabCompleter.create() + .at(0, CompletionSupplier.startsWith(() -> mains.stream().map(c -> c.getName().toLowerCase(Locale.ROOT)))) + .from(1, partial -> mains.stream() + .filter(m -> m.getName().equalsIgnoreCase(arguments.get(0))) + .findFirst() + .map(cmd -> cmd.tabComplete(this.plugin, sender, arguments.subList(1, arguments.size()))) + .orElse(Collections.emptyList()) + ) + .complete(arguments); + } + + private void sendCommandUsage(ServerPlayer sender, String label) { + sender.sendMessage(Message.prefixed(Component.text() + .color(NamedTextColor.DARK_GREEN) + .append(Component.text("Running ")) + .append(Component.text("Aurionchat", NamedTextColor.AQUA)) + )); + + this.mainCommands.values().stream() + .filter(c -> c.isAuthorized(sender)) + .forEach(c -> sender.sendMessage(Component.text() + .append(Component.text('>', NamedTextColor.DARK_AQUA)) + .append(Component.space()) + .append(Component.text(String.format(c.getUsage(), label), NamedTextColor.GREEN)) + .clickEvent(ClickEvent.suggestCommand(String.format(c.getUsage(), label))) + .build() + )); + } +} diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/CommandSpec.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/CommandSpec.java new file mode 100644 index 0000000..db5ea1b --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/CommandSpec.java @@ -0,0 +1,76 @@ +package com.mineaurion.aurionchat.common.command; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public enum CommandSpec { + + BALANCE("/%s balance ", + arg("user", false)), + + WITHDRAW("/%s withdraw ", + arg("user", true), + arg("amount", true) + ), + + ADD("/%s add ", + arg("user", true), + arg("amount", true) + ), + + SET("/%s set ", + arg("user", true), + arg("amount", true) + ), + PAY("/%s pay ", + arg("user", true), + arg("amount", true) + ), + CHECK("/%s check ", + arg("user", true), + arg("amount", true), + arg("commands...", true) + ), + + TOP("/%s top"), + + LOG("/%s log"); + + private final String usage; + private final List args; + + CommandSpec(String usage, PartialArgument... args){ + this.usage = usage; + this.args = args.length == 0 ? null : Arrays.stream(args) + .map(builder -> { + String key = builder.id.replace(".", "").replace(' ', '-'); + return new Argument(builder.name, builder.required); + }) + .collect(Collectors.toList()); + } + + public String usage(){ + return this.usage; + } + + public List args(){ + return this.args; + } + + private static PartialArgument arg(String name, boolean required){ + return new PartialArgument(name, name, required); + } + + private static final class PartialArgument { + private final String id; + private final String name; + private final boolean required; + + private PartialArgument(String id, String name, boolean required){ + this.id = id; + this.name = name; + this.required = required; + } + } +}; \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/SingleCommand.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/SingleCommand.java new file mode 100644 index 0000000..2f60b27 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/SingleCommand.java @@ -0,0 +1,48 @@ +package com.mineaurion.aurionchat.common.command; + +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.JoinConfiguration; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.NamedTextColor; + +import java.util.List; +import java.util.Locale; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public abstract class SingleCommand extends AbstractCommand { + + public SingleCommand(CommandSpec spec, String name, String permission, Predicate argumentCheck){ + super(spec, name, permission, argumentCheck); + } + + public SingleCommand(CommandSpec spec, String name, String permission, String adminPermission, Predicate argumentCheck){ + super(spec, name, permission, adminPermission, argumentCheck); + } + + public abstract void execute(AurionChatPlugin plugin, ServerPlayer sender, List args, String label) throws Exception; + + @Override + public void sendUsage(ServerPlayer sender, String label) { + TextComponent.Builder builder = Component.text() + .append(Component.text('>', NamedTextColor.DARK_AQUA)) + .append(Component.space()) + .append(Component.text(getName().toLowerCase(Locale.ROOT), NamedTextColor.GREEN)); + + if(getArgs().isPresent()){ + List argUsages = getArgs().get().stream() + .map(Argument::asComponent) + .collect(Collectors.toList()); + + builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA)) + .append(Component.join(JoinConfiguration.separator(Component.space()), argUsages)); + } + sender.sendMessage(builder.build()); + } + +// public Optional getPlayerUUID(AurionChatPlugin plugin, List args, int index){ +// return Utils.isValidUUID(args.get(index)) ? Optional.of(UUID.fromString(args.get(index))) : plugin.lookupUUID(args.get(index)); +// } +} diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/sender/AbstractSender.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/sender/AbstractSender.java new file mode 100644 index 0000000..b1a5820 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/sender/AbstractSender.java @@ -0,0 +1,135 @@ +package com.mineaurion.aurionchat.common.command.sender; + +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.JoinConfiguration; +import net.kyori.adventure.text.TextComponent; + +import java.util.*; +import java.util.stream.Collectors; + +public class AbstractSender implements ServerPlayer { + + private final AurionChatPlugin plugin; + private final SenderFactory factory; + private final T sender; + + private final UUID id; + private final String prefix; + private final String suffix; + private final String name; + private final String displayName; + private final boolean isConsole; + + public AbstractSender(AurionChatPlugin plugin, SenderFactory factory, T sender){ + this.plugin = plugin; + this.factory = factory; + this.sender = sender; + this.id = factory.getId(this.sender); + this.name = factory.getName(this.sender); + this.displayName = factory.getDisplayName(this.sender); + this.prefix = factory.getPrefix(this.sender); + this.suffix = factory.getSuffix(this.sender); + this.isConsole = this.factory.isConsole(this.sender); + } + + public AurionChatPlugin getPlugin(){ + return this.plugin; + } + + @Override + public String getDisplayName() { + return this.displayName; + } + + @Override + public UUID getId() { + return this.id; + } + + @Override + public String getPrefix() { + return this.prefix; + } + + @Override + public String getSuffix() { + return this.suffix; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public void sendMessage(Component message) { + if(isConsole()){ + for(Component line: splitNewlines(message)){ + this.factory.sendMessage(this.sender, line); + } + } else { + this.factory.sendMessage(this.sender, message); + } + } + + @Override + public boolean hasPermission(String permission) { + return isConsole() || this.factory.hasPermission(this.sender, permission); + } + + @Override + public boolean isConsole() { + return this.isConsole; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if(!(obj instanceof AbstractSender)) return false; + final AbstractSender that = (AbstractSender) obj; + return this.getId().equals(that.getId()); + } + + @Override + public int hashCode() { + return this.id.hashCode(); + } + + // Small function to split components using join into separate component + public static Iterable splitNewlines(Component message) { + if (message instanceof TextComponent && message.style().isEmpty() && !message.children().isEmpty() && ((TextComponent) message).content().isEmpty()) { + LinkedList> split = new LinkedList<>(); + split.add(new ArrayList<>()); + + for (Component child : message.children()) { + if (Component.newline().equals(child)) { + split.add(new ArrayList<>()); + } else { + Iterator splitChildren = splitNewlines(child).iterator(); + if (splitChildren.hasNext()) { + split.getLast().add(splitChildren.next()); + } + while (splitChildren.hasNext()) { + split.add(new ArrayList<>()); + split.getLast().add(splitChildren.next()); + } + } + } + + return split.stream().map(input -> { + switch (input.size()) { + case 0: + return Component.empty(); + case 1: + return input.get(0); + default: + return Component.join(JoinConfiguration.builder().build(), input); + } + }).collect(Collectors.toList()); + } + + return Collections.singleton(message); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/sender/DummyConsoleSender.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/sender/DummyConsoleSender.java new file mode 100644 index 0000000..10d6cb6 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/sender/DummyConsoleSender.java @@ -0,0 +1,30 @@ +package com.mineaurion.aurionchat.common.command.sender; + +import com.mineaurion.aurionchat.api.model.ServerPlayer; + +import java.util.UUID; + +public abstract class DummyConsoleSender implements ServerPlayer { + + public DummyConsoleSender(){} + + @Override + public boolean hasPermission(String permission) { + return true; + } + + @Override + public boolean isConsole() { + return true; + } + + @Override + public UUID getId() { + return CONSOLE_UUID; + } + + @Override + public String getName() { + return CONSOLE_NAME; + } +} diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/sender/SenderFactory.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/sender/SenderFactory.java new file mode 100644 index 0000000..882bcab --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/sender/SenderFactory.java @@ -0,0 +1,51 @@ +package com.mineaurion.aurionchat.common.command.sender; + +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import com.mineaurion.aurionchat.common.LuckPermsUtils; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; +import net.kyori.adventure.text.Component; +import net.luckperms.api.LuckPermsProvider; + +import java.util.Objects; +import java.util.UUID; + +public abstract class SenderFactory

implements AutoCloseable { + + private final P plugin; + + private LuckPermsUtils luckPermsUtils = null; + + public SenderFactory(P plugin, boolean withLuckPerms){ + this.plugin = plugin; + if(withLuckPerms){ + luckPermsUtils = new LuckPermsUtils(LuckPermsProvider.get()); + } + } + + protected P getPlugin(){ + return this.plugin; + } + + protected abstract UUID getId(T sender); + protected abstract String getName(T sender); + protected abstract String getDisplayName(T sender); + protected abstract void sendMessage(T sender, Component message); + protected abstract boolean hasPermission(T sender, String permission); + protected abstract boolean isConsole(T sender); + + public String getPrefix(T player){ + return luckPermsUtils != null ? luckPermsUtils.getPlayerPreffix(getId(player)).orElse("") : ""; + }; + + public String getSuffix(T player){ + return luckPermsUtils != null ? luckPermsUtils.getPlayerSuffix(getId(player)).orElse("") : ""; + }; + + public final ServerPlayer wrap(T sender){ + Objects.requireNonNull(sender, "sender"); + return new AbstractSender<>(this.plugin, this, sender); + } + + @Override + public void close() {} +} \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/tabcomplete/CompletionSupplier.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/tabcomplete/CompletionSupplier.java new file mode 100644 index 0000000..2f0ce39 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/tabcomplete/CompletionSupplier.java @@ -0,0 +1,16 @@ +package com.mineaurion.aurionchat.common.command.tabcomplete; + +import com.mineaurion.aurionchat.common.misc.Predicates; + +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public interface CompletionSupplier { + static CompletionSupplier startsWith(Supplier> stringsSupplier) { + return partial -> stringsSupplier.get().filter(Predicates.startsWithIgnoreCase(partial)).collect(Collectors.toList()); + } + + List supplyCompletions(String partial); +} diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/tabcomplete/TabCompleter.java b/common/src/main/java/com/mineaurion/aurionchat/common/command/tabcomplete/TabCompleter.java new file mode 100644 index 0000000..13a6ea1 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/command/tabcomplete/TabCompleter.java @@ -0,0 +1,71 @@ +package com.mineaurion.aurionchat.common.command.tabcomplete; + +import com.google.common.base.Preconditions; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class TabCompleter { + + public static TabCompleter create() { + return new TabCompleter(); + } + + private final Map suppliers = new HashMap<>(); + private int from = Integer.MAX_VALUE; + + private TabCompleter() {} + + /** + * Marks that the given completion supplier should be used to compute tab + * completions at the given index. + * + * @param position the position + * @param supplier the supplier + * @return this + */ + public TabCompleter at(int position, CompletionSupplier supplier) { + Preconditions.checkState(position < this.from); + this.suppliers.put(position, supplier); + return this; + } + + /** + * Marks that the given completion supplier should be used to compute tab + * completions at the given index and at all subsequent indexes infinitely. + * + * @param position the position + * @param supplier the supplier + * @return this + */ + public TabCompleter from(int position, CompletionSupplier supplier) { + Preconditions.checkState(this.from == Integer.MAX_VALUE); + this.suppliers.put(position, supplier); + this.from = position; + return this; + } + + public List complete(List args) { + int lastIndex = 0; + String partial; + + // nothing entered yet + if (args.isEmpty() || (partial = args.get(lastIndex = args.size() - 1)).trim().isEmpty()) { + return getCompletions(lastIndex, ""); + } + + // started typing something + return getCompletions(lastIndex, partial); + } + + private List getCompletions(int position, String partial) { + if (position >= this.from) { + return this.suppliers.get(this.from).supplyCompletions(partial); + } + return this.suppliers.getOrDefault(position, empty -> Collections.emptyList()).supplyCompletions(partial); + } + +} diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/command/ChatCommandCommon.java b/common/src/main/java/com/mineaurion/aurionchat/common/commands/ChatCommandCommon.java similarity index 95% rename from common/src/main/java/com/mineaurion/aurionchat/common/command/ChatCommandCommon.java rename to common/src/main/java/com/mineaurion/aurionchat/common/commands/ChatCommandCommon.java index 9a966a8..5f594be 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/command/ChatCommandCommon.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/commands/ChatCommandCommon.java @@ -1,11 +1,11 @@ -package com.mineaurion.aurionchat.common.command; +package com.mineaurion.aurionchat.common.commands; import com.mineaurion.aurionchat.api.AurionPacket; -import com.mineaurion.aurionchat.common.AbstractAurionChat; import com.mineaurion.aurionchat.common.AurionChatPlayer; import com.mineaurion.aurionchat.common.Utils; import com.mineaurion.aurionchat.common.exception.ChannelNotFoundException; import com.mineaurion.aurionchat.common.exception.InsufficientPermissionException; +import com.mineaurion.aurionchat.common.plugin.AbstractAurionChat; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/listeners/LoginListenerCommon.java b/common/src/main/java/com/mineaurion/aurionchat/common/listeners/LoginListenerCommon.java index ae55a61..8f3d999 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/listeners/LoginListenerCommon.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/listeners/LoginListenerCommon.java @@ -1,8 +1,8 @@ package com.mineaurion.aurionchat.common.listeners; import com.mineaurion.aurionchat.api.model.ServerPlayer; -import com.mineaurion.aurionchat.common.AbstractAurionChat; import com.mineaurion.aurionchat.common.AurionChatPlayer; +import com.mineaurion.aurionchat.common.plugin.AbstractAurionChat; public abstract class LoginListenerCommon { public T plugin; diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/locale/Message.java b/common/src/main/java/com/mineaurion/aurionchat/common/locale/Message.java new file mode 100644 index 0000000..0a512a2 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/locale/Message.java @@ -0,0 +1,123 @@ +package com.mineaurion.aurionchat.common.locale; + +import com.mineaurion.aurionchat.common.command.sender.Sender; +import com.mineaurion.aurionchat.common.config.Channel; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.JoinConfiguration; +import net.kyori.adventure.text.TextComponent; + +import java.util.List; + +import static net.kyori.adventure.text.Component.*; +import static net.kyori.adventure.text.format.NamedTextColor.*; +import static net.kyori.adventure.text.format.TextDecoration.BOLD; + +public interface Message { + + Component PREFIX_COMPONENT = text() + .color(GRAY) + .append(text('[')) + .append(text() + .decoration(BOLD, true) + .append(text(AurionChatPlugin.NAME, AQUA)) + ) + .append(text(']')) + .build(); + + static TextComponent prefixed(ComponentLike component){ + return text() + .append(PREFIX_COMPONENT) + .append(space()) + .append(component) + .build(); + } + Args0 COMMAND_NO_PERMISSION = () -> prefixed(text("You do no have permission to use this command")).color(RED); + + Args1 CHANNEL_JOIN = (channel) -> joinNewline( + prefixed(text() + .color(AQUA) + .append(text(String.format("You have joined the channel %s", channel), WHITE)) + ) + ); + + Args1 CHANNEL_LEAVE = (channel) -> joinNewline( + prefixed(text() + .color(AQUA) + .append(text(String.format("You have leaved the channel %s", channel), WHITE)) + ) + ); + + Args1 CHANNEL_SPY = (channel) -> joinNewline( + prefixed(text() + .color(AQUA) + .append(text(String.format("You have listenned the channel %s", channel), WHITE)) + ) + ); + + Args0 CHANNEL_ALLLISTEN = () -> prefixed(text("You have listenned to all channel")).color(WHITE); + + Args1> CHANNEL_INFO = (channels) -> joinNewline( + prefixed(text() + .color(AQUA) + .append(text("- ", WHITE)) + .append(text("Player: ")) + ), + prefixed(text() + .color(AQUA) + .append(text("- ", WHITE)) + .append(text("Amount: ")) + ) + ); + + Args1 CHANNEL_NOT_FOUND = (channel) -> joinNewline( + prefixed(text() + .color(AQUA) + .append(text(String.format("The channel %s was not found", channel), WHITE)) + ) + ); + + Args1 REQUIRED_ARGUMENT = name -> text() + .color(DARK_GRAY) + .append(text('<')) + .append(text(name, GRAY)) + .append(text('>')) + .build(); + + Args1 OPTIONAL_ARGUMENT = name -> text() + .color(DARK_GRAY) + .append(text('[')) + .append(text(name, GRAY)) + .append(text(']')) + .build(); + + + static Component joinNewline(final ComponentLike... components) { + return join(JoinConfiguration.newlines(), components); + } + + interface Args0 { + Component build(); + + default void send(Sender sender) { + sender.sendMessage(build()); + } + } + + interface Args1 { + Component build(A0 arg0); + + default void send(Sender sender, A0 arg0) { + sender.sendMessage(build(arg0)); + } + } + + interface Args2 { + Component build(A0 arg0, A1 arg1); + + default void send(Sender sender, A0 arg0, A1 arg1) { + sender.sendMessage(build(arg0, arg1)); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/misc/ArgumentTokenizer.java b/common/src/main/java/com/mineaurion/aurionchat/common/misc/ArgumentTokenizer.java new file mode 100644 index 0000000..cd1c498 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/misc/ArgumentTokenizer.java @@ -0,0 +1,22 @@ +package com.mineaurion.aurionchat.common.misc; + +import java.util.List; + +public enum ArgumentTokenizer { + + EXECUTE { + @Override + public List tokenizeInput(String args) { + return new QuotedStringTokenizer(args).tokenize(true); + } + }, + TAB_COMPLETE { + @Override + public List tokenizeInput(String args) { + return new QuotedStringTokenizer(args).tokenize(false); + } + }; + + public abstract List tokenizeInput(String args); + +} \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/misc/ImmutableCollectors.java b/common/src/main/java/com/mineaurion/aurionchat/common/misc/ImmutableCollectors.java new file mode 100644 index 0000000..7fb224d --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/misc/ImmutableCollectors.java @@ -0,0 +1,19 @@ +package com.mineaurion.aurionchat.common.misc; + +import com.google.common.collect.ImmutableMap; + +import java.util.function.Function; +import java.util.stream.Collector; + +public final class ImmutableCollectors { + private ImmutableCollectors() {} + + public static Collector, ImmutableMap> toMap(Function keyMapper, Function valueMapper) { + return Collector.of( + ImmutableMap.Builder::new, + (r, t) -> r.put(keyMapper.apply(t), valueMapper.apply(t)), + (l, r) -> l.putAll(r.build()), + ImmutableMap.Builder::build + ); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/misc/Predicates.java b/common/src/main/java/com/mineaurion/aurionchat/common/misc/Predicates.java new file mode 100644 index 0000000..25f1932 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/misc/Predicates.java @@ -0,0 +1,50 @@ +package com.mineaurion.aurionchat.common.misc; + +import com.google.common.collect.Range; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.function.Predicate; +public final class Predicates { + + private Predicates() {} + + @SuppressWarnings("rawtypes") + private static final Predicate FALSE = new Predicate() { + @Override public boolean test(Object o) { return false; } + @Override + public @NonNull Predicate and(@NonNull Predicate other) { return this; } + @Override + public @NonNull Predicate or(@NonNull Predicate other) { return other; } + @Override + public @NonNull Predicate negate() { return TRUE; } + }; + @SuppressWarnings("rawtypes") + private static final Predicate TRUE = new Predicate() { + @Override public boolean test(Object o) { return true; } + @Override + public @NonNull Predicate and(@NonNull Predicate other) { return other; } + @Override + public @NonNull Predicate or(@NonNull Predicate other) { return this; } + @Override + public @NonNull Predicate negate() { return FALSE; } + }; + + @SuppressWarnings("unchecked") + public static Predicate alwaysFalse() { + return FALSE; + } + + public static Predicate inRange(int start, int end) { + Range range = Range.closed(start, end); + return range::contains; + } + + public static Predicate startsWithIgnoreCase(String prefix) { + return string -> { + if (string.length() < prefix.length()) { + return false; + } + return string.regionMatches(true, 0, prefix, 0, prefix.length()); + }; + } +} \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/misc/QuotedStringTokenizer.java b/common/src/main/java/com/mineaurion/aurionchat/common/misc/QuotedStringTokenizer.java new file mode 100644 index 0000000..ae75867 --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/misc/QuotedStringTokenizer.java @@ -0,0 +1,91 @@ +package com.mineaurion.aurionchat.common.misc; + +import java.util.ArrayList; +import java.util.List; + +public class QuotedStringTokenizer { + private final String string; + private int cursor; + + public QuotedStringTokenizer(String string) { + this.string = string; + } + + public List tokenize(boolean omitEmptyStringAtEnd) { + List output = new ArrayList<>(); + while (hasNext()) { + output.add(readString()); + } + if (!omitEmptyStringAtEnd && this.cursor > 0 && isWhitespace(peek(-1))) { + output.add(""); + } + return output; + } + + private static boolean isQuoteCharacter(char c) { + // return c == '"' || c == '“' || c == '”'; + return c == '\u0022' || c == '\u201C' || c == '\u201D'; + } + + private static boolean isWhitespace(char c) { + return c == ' '; + } + + private String readString() { + if (isQuoteCharacter(peek())) { + return readQuotedString(); + } else { + return readUnquotedString(); + } + } + + private String readUnquotedString() { + final int start = this.cursor; + while (hasNext() && !isWhitespace(peek())) { + skip(); + } + final int end = this.cursor; + + if (hasNext()) { + skip(); // skip whitespace + } + + return this.string.substring(start, end); + } + + private String readQuotedString() { + skip(); // skip start quote + + final int start = this.cursor; + while (hasNext() && !isQuoteCharacter(peek())) { + skip(); + } + final int end = this.cursor; + + if (hasNext()) { + skip(); // skip end quote + } + if (hasNext() && isWhitespace(peek())) { + skip(); // skip whitespace + } + + return this.string.substring(start, end); + } + + private boolean hasNext() { + return this.cursor + 1 <= this.string.length(); + } + + private char peek() { + return this.string.charAt(this.cursor); + } + + private char peek(int offset) { + return this.string.charAt(this.cursor + offset); + } + + private void skip() { + this.cursor++; + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/AbstractAurionChat.java b/common/src/main/java/com/mineaurion/aurionchat/common/plugin/AbstractAurionChat.java similarity index 62% rename from common/src/main/java/com/mineaurion/aurionchat/common/AbstractAurionChat.java rename to common/src/main/java/com/mineaurion/aurionchat/common/plugin/AbstractAurionChat.java index 783b2fc..e84a233 100644 --- a/common/src/main/java/com/mineaurion/aurionchat/common/AbstractAurionChat.java +++ b/common/src/main/java/com/mineaurion/aurionchat/common/plugin/AbstractAurionChat.java @@ -1,8 +1,8 @@ -package com.mineaurion.aurionchat.common; +package com.mineaurion.aurionchat.common.plugin; -import com.mineaurion.aurionchat.common.config.ConfigurationAdapter; +import com.mineaurion.aurionchat.common.AurionChatPlayer; +import com.mineaurion.aurionchat.common.ChatService; import com.mineaurion.aurionchat.common.logger.PluginLogger; -import com.mineaurion.aurionchat.common.player.PlayerFactory; import java.io.IOException; import java.io.InputStream; @@ -14,21 +14,25 @@ public abstract class AbstractAurionChat implements AurionChatPlugin { - public static final String ID = "aurionchat"; + private final PluginLogger logger; private Map aurionChatPlayers; private ChatService chatService; - - private final PluginLogger logger = getlogger(); + + public AbstractAurionChat(PluginLogger logger){ + this.logger = logger; + } + public final void enable(){ + logger.info(AurionChatPlugin.NAME + " starting"); //send message for startup try { aurionChatPlayers = new HashMap<>(); chatService = new ChatService(this); logger.info("AurionChat Connected to Rabbitmq"); - setupPlayerFactory(); + setupSenderFactory(); registerPlatformListeners(); // if no error , init of the "plugin" registerCommands(); } catch (IOException e) { @@ -47,7 +51,7 @@ public final void disable(){ } protected Path resolveConfig(String filename){ - Path configFile = getConfigDirectory().resolve(filename); + Path configFile = getBootstrap().getConfigDirectory().resolve(filename); if(!Files.exists(configFile)){ try { @@ -56,7 +60,7 @@ protected Path resolveConfig(String filename){ //ignore } - try(InputStream is = getRessourceStream(filename)){ + try(InputStream is = getBootstrap().getResourceStream(filename)){ Files.copy(is, configFile); } catch (IOException e) { throw new RuntimeException(e); @@ -75,30 +79,17 @@ public Map getAurionChatPlayers() { protected abstract void registerPlatformListeners(); - protected abstract void setupPlayerFactory(); + protected abstract void setupSenderFactory(); + + // protected abstract void setupPlayerFactory(); protected abstract void registerCommands(); protected abstract void disablePlugin(); - public abstract ConfigurationAdapter getConfigurationAdapter(); - - public abstract PlayerFactory getPlayerFactory(); - - /** - * Gets the plugins main data storage directory - * - *

Bukkit: ./plugins/Economy

- *

Sponge: ./Economy/

- *

Fabric: ./mods/Economy

- *

Forge: ./config/Economy

- * - * @return the platforms data folder - */ - protected abstract Path getConfigDirectory(); - - private InputStream getRessourceStream(String path){ - return getClass().getClassLoader().getResourceAsStream(path); + @Override + public PluginLogger getLogger(){ + return this.logger; } } diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/plugin/AurionChatBootstrap.java b/common/src/main/java/com/mineaurion/aurionchat/common/plugin/AurionChatBootstrap.java new file mode 100644 index 0000000..5eef70e --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/plugin/AurionChatBootstrap.java @@ -0,0 +1,22 @@ +package com.mineaurion.aurionchat.common.plugin; + +import java.io.InputStream; +import java.nio.file.Path; + +public interface AurionChatBootstrap { + /** + * Gets the plugins main data storage directory + * + *

Bukkit: ./plugins/Aurionchat

+ *

Sponge: ./Aurionchat/

+ *

Fabric: ./mods/Aurionchat

+ *

Forge: ./config/Aurionchat

+ * + * @return the platforms data folder + */ + Path getConfigDirectory(); + + default InputStream getResourceStream(String path){ + return getClass().getClassLoader().getResourceAsStream(path); + } +} diff --git a/common/src/main/java/com/mineaurion/aurionchat/common/plugin/AurionChatPlugin.java b/common/src/main/java/com/mineaurion/aurionchat/common/plugin/AurionChatPlugin.java new file mode 100644 index 0000000..a02b47e --- /dev/null +++ b/common/src/main/java/com/mineaurion/aurionchat/common/plugin/AurionChatPlugin.java @@ -0,0 +1,20 @@ +package com.mineaurion.aurionchat.common.plugin; + +import com.mineaurion.aurionchat.common.ChatService; +import com.mineaurion.aurionchat.common.config.ConfigurationAdapter; +import com.mineaurion.aurionchat.common.logger.PluginLogger; + +public interface AurionChatPlugin { + + String MOD_ID = "aurionchat"; + + String NAME = "AurionChat"; + + AurionChatBootstrap getBootstrap(); + + PluginLogger getLogger(); + + ChatService getChatService(); + + ConfigurationAdapter getConfigurationAdapter(); +} diff --git a/common/src/test/java/TestMessageProcessing.java b/common/src/test/java/TestMessageProcessing.java index 77f1c48..f26f4aa 100644 --- a/common/src/test/java/TestMessageProcessing.java +++ b/common/src/test/java/TestMessageProcessing.java @@ -1,7 +1,7 @@ import com.mineaurion.aurionchat.api.model.ServerPlayer; -import com.mineaurion.aurionchat.common.AbstractAurionChat; import com.mineaurion.aurionchat.common.AurionChatPlayer; import com.mineaurion.aurionchat.common.config.ConfigurationAdapter; +import com.mineaurion.aurionchat.common.plugin.AbstractAurionChat; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import org.junit.After; diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/AurionChat.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/AurionChat.java index 1128f89..9b3e4cd 100644 --- a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/AurionChat.java +++ b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/AurionChat.java @@ -1,36 +1,39 @@ package com.mineaurion.aurionchat.fabric; -import com.mineaurion.aurionchat.common.AbstractAurionChat; import com.mineaurion.aurionchat.common.config.ConfigurationAdapter; -import com.mineaurion.aurionchat.common.logger.PluginLogger; -import com.mineaurion.aurionchat.common.logger.Slf4jPluginLogger; -import com.mineaurion.aurionchat.fabric.command.ChatCommand; +import com.mineaurion.aurionchat.common.logger.Log4jPluginLogger; +import com.mineaurion.aurionchat.common.plugin.AbstractAurionChat; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; import com.mineaurion.aurionchat.fabric.listeners.ChatListener; import com.mineaurion.aurionchat.fabric.listeners.LoginListener; -import net.fabricmc.api.DedicatedServerModInitializer; -import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.message.v1.ServerMessageEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; -import net.fabricmc.loader.api.FabricLoader; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minecraft.text.Text; -import org.slf4j.LoggerFactory; +import org.apache.logging.log4j.LogManager; -import java.nio.file.Path; +public class AurionChat extends AbstractAurionChat { -public class AurionChat extends AbstractAurionChat implements DedicatedServerModInitializer { + public final Bootstrap bootstrap; - private PlayerFactory playerFactory; + private SenderFactory senderFactory; + + private CommandExecutor commandManager; + + public AurionChat(Bootstrap bootstrap){ + super(new Log4jPluginLogger(LogManager.getLogger(AurionChatPlugin.NAME))); + this.bootstrap = bootstrap; + } @Override - public void onInitializeServer() { - getlogger().info("AurionChat Initializing"); - ServerLifecycleEvents.SERVER_STARTED.register(server -> this.enable()); + public Bootstrap getBootstrap() { + return bootstrap; + } - CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> new ChatCommand(this, dispatcher)); - ServerLifecycleEvents.SERVER_STOPPED.register(server -> this.disable()); + @Override + protected void setupSenderFactory() { + this.senderFactory = new SenderFactory(this); } @Override @@ -43,11 +46,6 @@ protected void registerPlatformListeners() { ServerMessageEvents.ALLOW_CHAT_MESSAGE.register(chatListener); } - @Override - protected void setupPlayerFactory() { - this.playerFactory = new PlayerFactory(); - } - @Override protected void registerCommands() { // Nothing to do here for fabric @@ -56,27 +54,21 @@ protected void registerCommands() { @Override protected void disablePlugin() {} - @Override - public ConfigurationAdapter getConfigurationAdapter() { - return new FabricConfigAdapter(resolveConfig(AbstractAurionChat.ID + ".conf")); + protected void registerListeners(){ + this.commandManager = new CommandExecutor(this); + this.commandManager.register(); } - @Override - public PlayerFactory getPlayerFactory() { - return playerFactory; - } - - @Override - protected Path getConfigDirectory() { - return FabricLoader.getInstance().getGameDir().resolve("config").resolve(AbstractAurionChat.ID); + public static Text toNativeText(Component component) { + return Text.Serializer.fromJson(GsonComponentSerializer.gson().serialize(component)); } @Override - public PluginLogger getlogger() { - return new Slf4jPluginLogger(LoggerFactory.getLogger(AbstractAurionChat.ID)); + public ConfigurationAdapter getConfigurationAdapter() { + return new FabricConfigAdapter(resolveConfig(AbstractAurionChat.MOD_ID + ".conf")); } - public static Text toNativeText(Component component) { - return Text.Serializer.fromJson(GsonComponentSerializer.gson().serialize(component)); + public SenderFactory getSenderFactory() { + return senderFactory; } } diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/Bootstrap.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/Bootstrap.java new file mode 100644 index 0000000..e79badc --- /dev/null +++ b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/Bootstrap.java @@ -0,0 +1,54 @@ +package com.mineaurion.aurionchat.fabric; + +import com.mineaurion.aurionchat.common.plugin.AurionChatBootstrap; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; +import net.fabricmc.api.DedicatedServerModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.minecraft.server.MinecraftServer; + +import java.nio.file.Path; +import java.util.Optional; + +public class Bootstrap implements AurionChatBootstrap, DedicatedServerModInitializer { + private final ModContainer loader; + + private AurionChat plugin; + + + private MinecraftServer server; + + public Bootstrap(){ + this.loader = FabricLoader.getInstance().getModContainer(AurionChatPlugin.MOD_ID).orElseThrow(() -> new RuntimeException("Could not get the " + AurionChatPlugin.MOD_ID + " mod container")); + } + + @Override + public void onInitializeServer(){ + this.plugin = new AurionChat(this); + ServerLifecycleEvents.SERVER_STARTING.register(this::onServerStarting); + ServerLifecycleEvents.SERVER_STOPPING.register(this::onServerStopping); + this.plugin.registerListeners(); + } + + + @Override + public Path getConfigDirectory() { + return FabricLoader.getInstance().getGameDir().resolve("mods").resolve(AurionChatPlugin.MOD_ID); + } + + + public Optional getServer() { + return Optional.ofNullable(server); + } + + private void onServerStarting(MinecraftServer server){ + this.server = server; + this.plugin.enable(); + } + + private void onServerStopping(MinecraftServer server){ + this.plugin.disable(); + this.server = null; + } +} diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/CommandExecutor.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/CommandExecutor.java new file mode 100644 index 0000000..9eb858e --- /dev/null +++ b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/CommandExecutor.java @@ -0,0 +1,43 @@ +package com.mineaurion.aurionchat.fabric; + +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import com.mineaurion.aurionchat.common.command.BrigadierCommandManager; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.minecraft.server.command.ServerCommandSource; + +import static com.mojang.brigadier.arguments.StringArgumentType.greedyString; +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + + +public class CommandExecutor extends BrigadierCommandManager { + + private final AurionChat plugin; + + public CommandExecutor(AurionChat plugin){ + super(plugin); + this.plugin = plugin; + } + + public void register(){ + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + for(String alias: COMMAND_ALIASES){ + LiteralCommandNode command = literal(alias).executes(this).build(); + + ArgumentCommandNode arguments = argument("args", greedyString()) + .suggests(this) + .executes(this) + .build(); + command.addChild(arguments); + dispatcher.getRoot().addChild(command); + } + }); + } + + @Override + public ServerPlayer getSender(ServerCommandSource source) { + return this.plugin.getSenderFactory().wrap(source); + } +} \ No newline at end of file diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/PlayerFactory.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/PlayerFactory.java deleted file mode 100644 index 07a3af7..0000000 --- a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/PlayerFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.mineaurion.aurionchat.fabric; - -import net.kyori.adventure.text.Component; -import net.minecraft.server.network.ServerPlayerEntity; - -import java.util.UUID; - -import static com.mineaurion.aurionchat.fabric.AurionChat.toNativeText; - -public class PlayerFactory extends com.mineaurion.aurionchat.common.player.PlayerFactory { - - public PlayerFactory(){ - super(true); - } - - @Override - protected UUID getUUID(ServerPlayerEntity player) { - return player.getUuid(); - } - - @Override - protected String getName(ServerPlayerEntity player) { - return player.getDisplayName().getString(); - } - - @Override - protected void sendMessage(ServerPlayerEntity player, Component message) { - player.sendMessage(toNativeText(message)); - } -} diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/SenderFactory.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/SenderFactory.java new file mode 100644 index 0000000..27cba68 --- /dev/null +++ b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/SenderFactory.java @@ -0,0 +1,60 @@ +package com.mineaurion.aurionchat.fabric; + +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import com.mineaurion.aurionchat.fabric.mixin.ServerCommandSourceAccessor; +import net.kyori.adventure.text.Component; +import net.minecraft.server.command.CommandOutput; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.rcon.RconCommandOutput; + +import java.util.UUID; + +import static com.mineaurion.aurionchat.fabric.AurionChat.toNativeText; + +public class SenderFactory extends com.mineaurion.aurionchat.common.command.sender.SenderFactory { + + public SenderFactory(AurionChat plugin){ + super(plugin, true); + } + + @Override + protected UUID getId(ServerCommandSource sender) { + if(sender.getEntity() != null){ + return sender.getEntity().getUuid(); + } + return ServerPlayer.CONSOLE_UUID; + } + + @Override + protected String getName(ServerCommandSource sender) { + String name = sender.getName(); + if(sender.getEntity() != null && name.equals("Server")){ + return ServerPlayer.CONSOLE_NAME; + } + return name; + } + + // TODO: need to be tested + @Override + protected String getDisplayName(ServerCommandSource sender) { + return sender.getDisplayName().getString(); + } + + @Override + protected void sendMessage(ServerCommandSource sender, Component message) { + sender.sendFeedback(() -> toNativeText(message), false); + } + + @Override + protected boolean hasPermission(ServerCommandSource sender, String permission) { + return permission.contains("admin") ? sender.hasPermissionLevel(4) : sender.hasPermissionLevel(0); + } + + @Override + protected boolean isConsole(ServerCommandSource sender) { + CommandOutput output = ((ServerCommandSourceAccessor) sender).getOutput(); + return output == sender.getServer() || // console + output.getClass() == RconCommandOutput.class || // Rcon + (output == CommandOutput.DUMMY && sender.getName().equals("")); // Functions + } +} \ No newline at end of file diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/command/ChatCommand.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/command/ChatCommand.java deleted file mode 100644 index edd06c8..0000000 --- a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/command/ChatCommand.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.mineaurion.aurionchat.fabric.command; - -import com.mineaurion.aurionchat.common.AurionChatPlayer; -import com.mineaurion.aurionchat.common.command.ChatCommandCommon; -import com.mineaurion.aurionchat.fabric.AurionChat; -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.builder.ArgumentBuilder; -import com.mojang.brigadier.builder.RequiredArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.tree.LiteralCommandNode; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.minecraft.command.argument.MessageArgumentType; -import net.minecraft.server.command.CommandManager; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.Text; - -public class ChatCommand extends ChatCommandCommon { - public ChatCommand(AurionChat plugin, CommandDispatcher dispatcher) { - super(plugin); - register(dispatcher); - registerAliasChannels(dispatcher); - } - - public void register(CommandDispatcher dispatcher){ - RequiredArgumentBuilder channelArg = CommandManager.argument("channel", StringArgumentType.string()).suggests((context, builder) -> { - getPlugin().getConfigurationAdapter().getChannels().forEach((name, chanel) -> builder.suggest(name)); - return builder.buildFuture(); - }); - - LiteralCommandNode join = CommandManager.literal("join") - .then(channelArg.executes(ctx -> this.execute(ctx, Action.JOIN))) - .build(); - - LiteralCommandNode leave = CommandManager.literal("leave") - .then(channelArg.executes(ctx -> this.execute(ctx, Action.LEAVE))) - .build(); - - LiteralCommandNode spy = CommandManager.literal("spy") - .then(channelArg.executes(ctx -> this.execute(ctx, Action.SPY))) - .build(); - - LiteralCommandNode allListen = CommandManager.literal("allListen") - .executes(ctx -> this.execute(ctx, Action.ALLLISTEN)) - .build(); - - LiteralCommandNode reload = CommandManager.literal("reload") - .requires(commandSource -> commandSource.hasPermissionLevel(3)) - .then(channelArg.executes(ctx -> this.execute(ctx, Action.RELOAD))) - .build(); - - dispatcher.register( - CommandManager.literal("channel") - .then(join) - .then(leave) - .then(spy) - .then(allListen) - .then(reload) - .executes(ctx -> this.execute(ctx, Action.DEFAULT)) - ); - } - - private int execute(CommandContext ctx, Action action){ - try { - ServerPlayerEntity player = ctx.getSource().getPlayerOrThrow(); - AurionChatPlayer aurionChatPlayer = getPlugin().getAurionChatPlayers().get(player.getUuid()); - String channel; - try { - channel = StringArgumentType.getString(ctx, "channel"); - } catch (IllegalArgumentException e) { - channel = ""; - } - return this.execute(aurionChatPlayer, channel, action) ? 1 : 0; - } catch (CommandSyntaxException e) { - ctx.getSource().sendError(Text.of("You need to be a player to do this")); - return 0; - } - } - - private void registerAliasChannels(CommandDispatcher dispatcher){ - RequiredArgumentBuilder messageArg = CommandManager.argument("message", MessageArgumentType.message()); - getPlugin().getConfigurationAdapter().getChannels().forEach((name, channel) -> { - ArgumentBuilder> argBuilder = messageArg.executes(ctx -> (onCommand( - getPlugin().getAurionChatPlayers().get(ctx.getSource().getPlayer().getUuid()), - GsonComponentSerializer.gson().deserialize(Text.Serializer.toJson(MessageArgumentType.getMessage(ctx, "message"))), - name, - channel.format)) ? 1 : 0 - ); - dispatcher.register( - CommandManager.literal(channel.alias).requires(ServerCommandSource::isExecutedByPlayer).then(argBuilder) - ); - dispatcher.register( - CommandManager.literal(name).requires(ServerCommandSource::isExecutedByPlayer).then(argBuilder) - ); - }); - } -} diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java index 52d971d..6b7f114 100644 --- a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java +++ b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/ChatListener.java @@ -40,7 +40,7 @@ public boolean allowChatMessage(SignedMessage message, ServerPlayerEntity sender try { plugin.getChatService().send(packet); } catch (IOException e) { - this.plugin.getlogger().severe(e.getMessage()); + this.plugin.getLogger().severe(e.getMessage()); } // TODO: need to remove that. Need to adapt rabbitmq to a fanout exchange for the chat. // After we can use CHAT_MESSAGE instead of this event diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/LoginListener.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/LoginListener.java index 12530fb..bf13244 100644 --- a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/LoginListener.java +++ b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/listeners/LoginListener.java @@ -10,10 +10,10 @@ public LoginListener(AurionChat plugin){ } public void onPlayerJoin(ServerPlayerEntity player){ - playerJoin(plugin.getPlayerFactory().wrap(player)); + playerJoin(plugin.getSenderFactory().wrap(player.getCommandSource())); } public void onPlayerQuit(ServerPlayerEntity player){ - this.playerLeaving(plugin.getPlayerFactory().wrap(player)); + this.playerLeaving(plugin.getSenderFactory().wrap(player.getCommandSource())); } } diff --git a/fabric/src/main/java/com/mineaurion/aurionchat/fabric/mixin/ServerCommandSourceAccessor.java b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/mixin/ServerCommandSourceAccessor.java new file mode 100644 index 0000000..eeab094 --- /dev/null +++ b/fabric/src/main/java/com/mineaurion/aurionchat/fabric/mixin/ServerCommandSourceAccessor.java @@ -0,0 +1,14 @@ +package com.mineaurion.aurionchat.fabric.mixin; + +import net.minecraft.server.command.CommandOutput; +import net.minecraft.server.command.ServerCommandSource; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ServerCommandSource.class) +public interface ServerCommandSourceAccessor { + + @Accessor("output") + CommandOutput getOutput(); + +} \ No newline at end of file diff --git a/fabric/src/main/resources/aurionchat.mixins.json b/fabric/src/main/resources/aurionchat.mixins.json new file mode 100644 index 0000000..25a15e0 --- /dev/null +++ b/fabric/src/main/resources/aurionchat.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "com.mineaurion.aurionchat.fabric.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "ServerCommandSourceAccessor" + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index cf5d9be..0760d1f 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -22,9 +22,12 @@ "environment": "server", "entrypoints": { "server": [ - "com.mineaurion.aurionchat.fabric.AurionChat" + "com.mineaurion.aurionchat.fabric.Bootstrap" ] }, + "mixins": [ + "aurionchat.mixins.json" + ], "depends": { "fabricloader": ">=0.9.0", "fabric": "*", diff --git a/forge/src/main/java/com/mineaurion/aurionchat/forge/AurionChat.java b/forge/src/main/java/com/mineaurion/aurionchat/forge/AurionChat.java index a7066e9..6f58af2 100644 --- a/forge/src/main/java/com/mineaurion/aurionchat/forge/AurionChat.java +++ b/forge/src/main/java/com/mineaurion/aurionchat/forge/AurionChat.java @@ -1,69 +1,47 @@ package com.mineaurion.aurionchat.forge; -import com.mineaurion.aurionchat.common.AbstractAurionChat; import com.mineaurion.aurionchat.common.config.ConfigurationAdapter; import com.mineaurion.aurionchat.common.logger.Log4jPluginLogger; -import com.mineaurion.aurionchat.common.logger.PluginLogger; -import com.mineaurion.aurionchat.forge.command.ChatCommand; +import com.mineaurion.aurionchat.common.plugin.AbstractAurionChat; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; import com.mineaurion.aurionchat.forge.listeners.ChatListener; import com.mineaurion.aurionchat.forge.listeners.LoginListener; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.server.ServerAboutToStartEvent; -import net.minecraftforge.event.server.ServerStartedEvent; -import net.minecraftforge.event.server.ServerStoppedEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.IExtensionPoint.DisplayTest; -import net.minecraftforge.fml.ModLoadingContext; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.loading.FMLPaths; -import net.minecraftforge.network.NetworkConstants; import org.apache.logging.log4j.LogManager; -import java.lang.reflect.InvocationTargetException; -import java.nio.file.Path; -import java.util.function.Supplier; - -@Mod(AbstractAurionChat.ID) public class AurionChat extends AbstractAurionChat { - private PlayerFactory playerFactory; - - public AurionChat() { - getlogger().info("AurionChat Initializing"); - try { - ModLoadingContext.class.getDeclaredMethod("registerExtensionPoint", Class.class, Supplier.class) - .invoke( - ModLoadingContext.get(), - DisplayTest.class, - (Supplier) () -> new DisplayTest( - () -> NetworkConstants.IGNORESERVERONLY, - (a, b) -> true - ) - ); - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new RuntimeException(e); - } - MinecraftForge.EVENT_BUS.register(this); + private Bootstrap bootstrap; + + private SenderFactory senderFactory; + + private CommandExecutor commandManager; + + public AurionChat(Bootstrap bootstrap){ + super(new Log4jPluginLogger(LogManager.getLogger(AurionChatPlugin.NAME))); + this.bootstrap = bootstrap; + } + @Override + public Bootstrap getBootstrap() { + return bootstrap; } - @SubscribeEvent - public void serverAboutToStart(ServerAboutToStartEvent event){ - new ChatCommand(this, event.getServer().getCommands().getDispatcher()); + protected void registerEarlyListeners(){ + this.commandManager = new CommandExecutor(this); + this.bootstrap.registerListeners(this.commandManager); } - @SubscribeEvent - public void serverStarted(ServerStartedEvent event) - { - this.enable(); + @Override + protected void setupSenderFactory() { + this.senderFactory = new SenderFactory(this); } - @SubscribeEvent - public void serverStopped(ServerStoppedEvent event) { - this.disable(); + public SenderFactory getSenderFactory() { + return senderFactory; } @Override @@ -72,14 +50,9 @@ protected void registerPlatformListeners() { MinecraftForge.EVENT_BUS.register(new ChatListener(this)); } - @Override - protected void setupPlayerFactory() { - this.playerFactory = new PlayerFactory(); - } - @Override protected void registerCommands() { - // Nothing to do here for forge + // Not used for forge, registered in #registerEarlyListeners } @Override @@ -90,22 +63,7 @@ protected void disablePlugin() { @Override public ConfigurationAdapter getConfigurationAdapter() { - return new ForgeConfigAdapter(resolveConfig(AbstractAurionChat.ID + ".conf")); - } - - @Override - public PlayerFactory getPlayerFactory() { - return playerFactory; - } - - @Override - protected Path getConfigDirectory() { - return FMLPaths.CONFIGDIR.get().resolve(AbstractAurionChat.ID).toAbsolutePath(); - } - - @Override - public PluginLogger getlogger() { - return new Log4jPluginLogger(LogManager.getLogger(AurionChat.ID)); + return new ForgeConfigAdapter(resolveConfig(AurionChatPlugin.MOD_ID + ".conf")); } public static net.minecraft.network.chat.Component toNativeText(Component component){ diff --git a/forge/src/main/java/com/mineaurion/aurionchat/forge/Bootstrap.java b/forge/src/main/java/com/mineaurion/aurionchat/forge/Bootstrap.java new file mode 100644 index 0000000..fd68b50 --- /dev/null +++ b/forge/src/main/java/com/mineaurion/aurionchat/forge/Bootstrap.java @@ -0,0 +1,95 @@ +package com.mineaurion.aurionchat.forge; + +import com.mineaurion.aurionchat.common.plugin.AurionChatBootstrap; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; +import net.minecraft.server.MinecraftServer; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.server.ServerStartingEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.IExtensionPoint; +import net.minecraftforge.fml.ModContainer; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fml.loading.FMLEnvironment; +import net.minecraftforge.fml.loading.FMLPaths; +import net.minecraftforge.network.NetworkConstants; + +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.function.Supplier; + +@Mod(value = AurionChatPlugin.MOD_ID) +public class Bootstrap implements AurionChatBootstrap { + private final ModContainer loader; + + private AurionChat plugin; + + private MinecraftServer server; + + public Bootstrap(){ + this.loader = ModList.get().getModContainerByObject(this).orElse(null); + markAsNotRequiredClientSide(); + + if(FMLEnvironment.dist.isClient()){ + System.out.println("Skipping " + AurionChatPlugin.NAME + " init (not supported on the client"); + return; + } + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onCommonSetup); + + this.plugin = new AurionChat(this); + } + + public void onCommonSetup(FMLCommonSetupEvent event){ + MinecraftForge.EVENT_BUS.register(this); + this.plugin.registerEarlyListeners(); + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void onServerAboutToStart(ServerStartingEvent event){ + this.server = event.getServer(); + this.plugin.enable(); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onServerStopping(ServerStoppingEvent event){ + this.plugin.disable(); + MinecraftForge.EVENT_BUS.unregister(this); + this.server = null; + } + + @Override + public Path getConfigDirectory() { + return FMLPaths.CONFIGDIR.get().resolve(AurionChatPlugin.MOD_ID).toAbsolutePath(); + } + + public void registerListeners(Object target){ + MinecraftForge.EVENT_BUS.register(target); + } + + + private static void markAsNotRequiredClientSide() { + try { + ModLoadingContext.class.getDeclaredMethod("registerExtensionPoint", Class.class, Supplier.class) + .invoke( + ModLoadingContext.get(), + IExtensionPoint.DisplayTest.class, + (Supplier) () -> new IExtensionPoint.DisplayTest( + () -> NetworkConstants.IGNORESERVERONLY, + (a, b) -> true + ) + ); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new IllegalStateException(e); + } + } + + public Optional getServer(){ + return Optional.ofNullable(this.server); + } +} diff --git a/forge/src/main/java/com/mineaurion/aurionchat/forge/CommandExecutor.java b/forge/src/main/java/com/mineaurion/aurionchat/forge/CommandExecutor.java new file mode 100644 index 0000000..5961aa2 --- /dev/null +++ b/forge/src/main/java/com/mineaurion/aurionchat/forge/CommandExecutor.java @@ -0,0 +1,40 @@ +package com.mineaurion.aurionchat.forge; + +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import com.mineaurion.aurionchat.common.command.BrigadierCommandManager; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +public class CommandExecutor extends BrigadierCommandManager { + + private final AurionChat plugin; + + public CommandExecutor(AurionChat plugin){ + super(plugin); + this.plugin = plugin; + } + + @SubscribeEvent + public void onRegisterCommands(RegisterCommandsEvent event){ + for(String alias: COMMAND_ALIASES){ + LiteralCommandNode command = Commands.literal(alias).executes(this).build(); + ArgumentCommandNode arguments = Commands.argument("args", StringArgumentType.greedyString()) + .suggests(this) + .executes(this) + .build(); + command.addChild(arguments); + event.getDispatcher().getRoot().addChild(command); + } + } + + @Override + public ServerPlayer getSender(CommandSourceStack source) { + return this.plugin.getSenderFactory().wrap(source); + } + +} \ No newline at end of file diff --git a/forge/src/main/java/com/mineaurion/aurionchat/forge/PlayerFactory.java b/forge/src/main/java/com/mineaurion/aurionchat/forge/PlayerFactory.java deleted file mode 100644 index 92339bd..0000000 --- a/forge/src/main/java/com/mineaurion/aurionchat/forge/PlayerFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.mineaurion.aurionchat.forge; - -import net.kyori.adventure.text.Component; -import net.minecraft.server.level.ServerPlayer; - -import java.util.UUID; - -import static com.mineaurion.aurionchat.forge.AurionChat.toNativeText; - -public class PlayerFactory extends com.mineaurion.aurionchat.common.player.PlayerFactory { - - public PlayerFactory(){ - super(true); - } - - @Override - protected UUID getUUID(ServerPlayer player) { - return player.getUUID(); - } - - @Override - protected String getName(ServerPlayer player) { - return player.getDisplayName().getString(); - } - - @Override - protected void sendMessage(ServerPlayer player, Component message) { - player.sendSystemMessage(toNativeText(message)); - } -} diff --git a/forge/src/main/java/com/mineaurion/aurionchat/forge/SenderFactory.java b/forge/src/main/java/com/mineaurion/aurionchat/forge/SenderFactory.java new file mode 100644 index 0000000..438803d --- /dev/null +++ b/forge/src/main/java/com/mineaurion/aurionchat/forge/SenderFactory.java @@ -0,0 +1,63 @@ +package com.mineaurion.aurionchat.forge; + +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.rcon.RconConsoleSource; +import net.minecraft.world.entity.player.Player; + +import java.util.UUID; + +import static com.mineaurion.aurionchat.forge.AurionChat.toNativeText; + +public class SenderFactory extends com.mineaurion.aurionchat.common.command.sender.SenderFactory { + + public SenderFactory(AurionChat plugin){ + super(plugin, true); + } + + @Override + protected UUID getId(CommandSourceStack commandSource) { + if(commandSource.getEntity() instanceof Player){ + return commandSource.getEntity().getUUID(); + } + return ServerPlayer.CONSOLE_UUID; + } + + @Override + protected String getName(CommandSourceStack commandSource) { + if(commandSource.getEntity() instanceof Player){ + return commandSource.getTextName(); + } + return ServerPlayer.CONSOLE_NAME; + } + + // TODO: need to be tested + @Override + protected String getDisplayName(CommandSourceStack sender) { + return sender.getDisplayName().getString(); + } + + @Override + protected void sendMessage(CommandSourceStack sender, Component message) { + sender.sendSuccess(() -> toNativeText(message), false); + } + + @Override + protected boolean hasPermission(CommandSourceStack sender, String permission) { + // If admin permission we check if the sender is OP + return permission.contains("admin") ? sender.hasPermission(4) : sender.hasPermission(0); + } + + @Override + protected boolean isConsole(CommandSourceStack sender) { + CommandSource output = sender.source; + return output == sender.getServer() || // Console + output.getClass() == RconConsoleSource.class || // Rcon + (output == CommandSource.NULL && sender.getTextName().equals("")); // Functions + + } + + +} \ No newline at end of file diff --git a/forge/src/main/java/com/mineaurion/aurionchat/forge/command/ChatCommand.java b/forge/src/main/java/com/mineaurion/aurionchat/forge/command/ChatCommand.java deleted file mode 100644 index 663d913..0000000 --- a/forge/src/main/java/com/mineaurion/aurionchat/forge/command/ChatCommand.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.mineaurion.aurionchat.forge.command; - -import com.mineaurion.aurionchat.common.AurionChatPlayer; -import com.mineaurion.aurionchat.common.command.ChatCommandCommon; -import com.mineaurion.aurionchat.forge.AurionChat; -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.builder.ArgumentBuilder; -import com.mojang.brigadier.builder.RequiredArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.tree.LiteralCommandNode; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.Commands; -import net.minecraft.commands.arguments.MessageArgument; -import net.minecraft.network.chat.Component; -import net.minecraft.server.level.ServerPlayer; - -public class ChatCommand extends ChatCommandCommon { - public ChatCommand(AurionChat plugin, CommandDispatcher dispatcher){ - super(plugin); - register(dispatcher); - registerAliasChannels(dispatcher); - } - - public void register(CommandDispatcher dispatcher) - { - RequiredArgumentBuilder channelArg = Commands.argument("channel", StringArgumentType.string()).suggests(((context, builder) -> { - getPlugin().getConfigurationAdapter().getChannels().forEach((name, channel) -> builder.suggest(name)); - return builder.buildFuture(); - })); - - LiteralCommandNode join = Commands.literal("join") - .then(channelArg.executes(ctx -> this.execute(ctx, Action.JOIN))) - .build(); - - LiteralCommandNode leave = Commands.literal("leave") - .then(channelArg.executes(ctx -> this.execute(ctx, Action.LEAVE))) - .build(); - - LiteralCommandNode spy = Commands.literal("spy") - .then(channelArg.executes(ctx -> this.execute(ctx, Action.SPY))) - .build(); - - LiteralCommandNode allListen = Commands.literal("allListen") - .executes(ctx -> this.execute(ctx, Action.ALLLISTEN)) - .build(); - - LiteralCommandNode reload = Commands.literal("reload") - .requires((commandSource) -> commandSource.hasPermission(3)) - .executes(ctx -> this.execute(ctx, Action.RELOAD)) - .build(); - - - dispatcher.register( - Commands.literal("channel") - .then(join) - .then(leave) - .then(spy) - .then(allListen) - .then(reload) - .executes(ctx -> this.execute(ctx, Action.DEFAULT)) - ); - } - - public void registerAliasChannels(CommandDispatcher dispatcher){ - RequiredArgumentBuilder messageArg = Commands.argument("message", MessageArgument.message()); - - getPlugin().getConfigurationAdapter().getChannels().forEach((name, channel) -> { - ArgumentBuilder> argBuilder = messageArg.executes(ctx -> (onCommand( - getPlugin().getAurionChatPlayers().get(ctx.getSource().getPlayerOrException().getUUID()), - GsonComponentSerializer.gson().deserialize(Component.Serializer.toJson(MessageArgument.getMessage(ctx, "message"))), - name, - channel.format)) ? 1 : 0 - ); - - dispatcher.register( - Commands.literal(channel.alias).requires(commandSource -> commandSource.getEntity() != null).then(argBuilder) - ); - dispatcher.register( - Commands.literal(name).requires(commandSource -> commandSource.getEntity() != null).then(argBuilder) - ); - }); - } - - private int execute(CommandContext ctx, Action action) { - try { - ServerPlayer player = ctx.getSource().getPlayerOrException(); - AurionChatPlayer aurionChatPlayer = getPlugin().getAurionChatPlayers().get(player.getUUID()); - String channel; - try{ - channel = StringArgumentType.getString(ctx, "channel"); - } catch (IllegalArgumentException e){ - channel = ""; - } - return this.execute(aurionChatPlayer, channel , action) ? 1 : 0; - } catch (CommandSyntaxException e){ - ctx.getSource().sendFailure(Component.empty().append("You need to be a player to do this")); - return 0; - } - } -} \ No newline at end of file diff --git a/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/LoginListener.java b/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/LoginListener.java index 5c5e06f..43c9b62 100644 --- a/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/LoginListener.java +++ b/forge/src/main/java/com/mineaurion/aurionchat/forge/listeners/LoginListener.java @@ -2,7 +2,6 @@ import com.mineaurion.aurionchat.common.listeners.LoginListenerCommon; import com.mineaurion.aurionchat.forge.AurionChat; -import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -15,11 +14,11 @@ public LoginListener(AurionChat plugin){ @SubscribeEvent public void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { - playerJoin(plugin.getPlayerFactory().wrap((ServerPlayer) event.getEntity())); + playerJoin(plugin.getSenderFactory().wrap(event.getEntity().createCommandSourceStack())); } @SubscribeEvent public void onPlayerQuit(PlayerEvent.PlayerLoggedOutEvent event){ - playerLeaving(plugin.getPlayerFactory().wrap((ServerPlayer) event.getEntity())); + playerLeaving(plugin.getSenderFactory().wrap(event.getEntity().createCommandSourceStack())); } } diff --git a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/AurionChat.java b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/AurionChat.java index 5ad8ecb..e694858 100644 --- a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/AurionChat.java +++ b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/AurionChat.java @@ -1,98 +1,91 @@ package com.mineaurion.aurionchat.sponge; -import com.google.inject.Inject; -import com.mineaurion.aurionchat.common.AbstractAurionChat; import com.mineaurion.aurionchat.common.config.ConfigurationAdapter; -import com.mineaurion.aurionchat.common.logger.Log4jPluginLogger; -import com.mineaurion.aurionchat.common.logger.PluginLogger; -import com.mineaurion.aurionchat.sponge.command.ChatCommand; +import com.mineaurion.aurionchat.common.plugin.AbstractAurionChat; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; import com.mineaurion.aurionchat.sponge.listeners.ChatListener; import com.mineaurion.aurionchat.sponge.listeners.LoginListener; -import org.apache.logging.log4j.LogManager; import org.spongepowered.api.Server; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.Command; -import org.spongepowered.api.config.ConfigDir; import org.spongepowered.api.event.EventManager; import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.lifecycle.ConstructPluginEvent; import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; -import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; import org.spongepowered.plugin.PluginContainer; -import org.spongepowered.plugin.builtin.jvm.Plugin; import java.io.IOException; import java.io.InputStream; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; -@Plugin("aurionchat") public class AurionChat extends AbstractAurionChat { - private PlayerFactory playerFactory; + public final Bootstrap bootstrap; - @Inject - public PluginContainer container; + private SenderFactory senderFactory; + private CommandExecutor commandManager; - @Inject - @ConfigDir(sharedRoot = false) - private Path configDirectory; - - @Listener - public void Init(ConstructPluginEvent event) { - getlogger().info("AurionChat Initializing"); - this.enable(); - } - - @Listener - public void onServerStop(StoppingEngineEvent event){ - this.disable(); + public AurionChat(Bootstrap bootstrap){ + super(bootstrap.getLogger()); + this.bootstrap = bootstrap; } @Override protected void registerPlatformListeners() { EventManager eventManager = Sponge.eventManager(); - eventManager.registerListeners(this.container, new LoginListener(this)); - eventManager.registerListeners(this.container, new ChatListener(this)); + eventManager.registerListeners(this.bootstrap.getPluginContainer(), new LoginListener(this)); + eventManager.registerListeners(this.bootstrap.getPluginContainer(), new ChatListener(this)); } @Override - protected void setupPlayerFactory() { - this.playerFactory = new PlayerFactory(); + protected void registerCommands() { + this.commandManager = new CommandExecutor(this); + this.bootstrap.registerListeners(new RegisterCommandsListener(this.bootstrap.getPluginContainer(), this.commandManager)); } @Override - protected void registerCommands() { - // look #onCommandRegister + protected void disablePlugin() { + Sponge.eventManager().unregisterListeners(this); } - @Listener - public void onCommandRegister(RegisterCommandEvent event){ - new ChatCommand(this, event); + public static final class RegisterCommandsListener { + private final PluginContainer pluginContainer; + private final Command.Raw command; + RegisterCommandsListener(PluginContainer pluginContainer, Command.Raw command){ + this.pluginContainer = pluginContainer; + this.command = command; + } + @Listener + public void onCommandRegister(RegisterCommandEvent event){ + event.register(this.pluginContainer, this.command, "aurionchat", "chat", "c"); + } } - @Override - protected void disablePlugin() { - Sponge.eventManager().unregisterListeners(this); + public SenderFactory getSenderFactory(){ + return this.senderFactory; + } + + public Optional getServer(){ + return this.bootstrap.getServer(); } @Override - public ConfigurationAdapter getConfigurationAdapter() { - return new SpongeConfigAdapter(resolveConfig()); + protected void setupSenderFactory() { + this.senderFactory = new SenderFactory(this); } @Override - public PlayerFactory getPlayerFactory() { - return playerFactory; + public ConfigurationAdapter getConfigurationAdapter() { + return new SpongeConfigAdapter(resolveConfig()); } private Path resolveConfig() { - Path path = getConfigDirectory().resolve(AurionChat.ID + ".conf"); + Path path = this.bootstrap.getConfigDirectory().resolve(AurionChatPlugin.MOD_ID + ".conf"); if (!Files.exists(path)) { try { - createDirectoriesIfNotExists(getConfigDirectory()); - try (InputStream is = getClass().getClassLoader().getResourceAsStream(AurionChat.ID + ".conf")) { + Bootstrap.createDirectoriesIfNotExists(this.bootstrap.getConfigDirectory()); + try (InputStream is = getClass().getClassLoader().getResourceAsStream(AurionChatPlugin.MOD_ID + ".conf")) { Files.copy(is, path); } } catch (IOException e) { @@ -102,25 +95,8 @@ private Path resolveConfig() { return path; } - public static void createDirectoriesIfNotExists(Path path) throws IOException { - if (Files.exists(path) && (Files.isDirectory(path) || Files.isSymbolicLink(path))) { - return; - } - - try { - Files.createDirectories(path); - } catch (FileAlreadyExistsException e) { - // ignore - } - } - - @Override - protected Path getConfigDirectory() { - return configDirectory; - } - @Override - public PluginLogger getlogger() { - return new Log4jPluginLogger(LogManager.getLogger(AurionChat.ID)); + public Bootstrap getBootstrap() { + return this.bootstrap; } } diff --git a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/Bootstrap.java b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/Bootstrap.java new file mode 100644 index 0000000..62bf8a2 --- /dev/null +++ b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/Bootstrap.java @@ -0,0 +1,97 @@ +package com.mineaurion.aurionchat.sponge; + +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.mineaurion.aurionchat.common.logger.Log4jPluginLogger; +import com.mineaurion.aurionchat.common.logger.PluginLogger; +import com.mineaurion.aurionchat.common.plugin.AurionChatBootstrap; +import com.mineaurion.aurionchat.common.plugin.AurionChatPlugin; +import org.apache.logging.log4j.LogManager; +import org.spongepowered.api.Game; +import org.spongepowered.api.Server; +import org.spongepowered.api.config.ConfigDir; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.lifecycle.ConstructPluginEvent; +import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; +import org.spongepowered.plugin.PluginContainer; +import org.spongepowered.plugin.builtin.jvm.Plugin; + +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.function.Supplier; + +@Plugin(AurionChatPlugin.MOD_ID) +public class Bootstrap implements AurionChatBootstrap, Supplier { + + private final Injector injector; + private AurionChat plugin; + + private final Game game; + private final PluginContainer pluginContainer; + + @Inject + @ConfigDir(sharedRoot = false) + private Path configDirectory; + + + @Inject + public Bootstrap(Injector injector){ + this.injector = injector; + this.game = injector.getInstance(Game.class); + this.pluginContainer = injector.getInstance(PluginContainer.class); + } + + @Override + public Injector get() { + return this.injector; + } + + @Listener(order = Order.FIRST) + public void onEnable(ConstructPluginEvent event){ + this.plugin = new AurionChat(this); + this.plugin.enable(); + } + + @Listener + public void onDisable(StoppingEngineEvent event){ + this.plugin.disable(); + } + + public PluginContainer getPluginContainer() { + return pluginContainer; + } + + public void registerListeners(Object obj){ + this.game.eventManager().registerListeners(this.pluginContainer, obj); + } + + @Override + public Path getConfigDirectory() { + return configDirectory; + } + + public Optional getServer() { + return this.game.isServerAvailable() ? Optional.of(this.game.server()) : Optional.empty(); + } + + public static void createDirectoriesIfNotExists(Path path) throws IOException { + if (Files.exists(path) && (Files.isDirectory(path) || Files.isSymbolicLink(path))) { + return; + } + + try { + Files.createDirectories(path); + } catch (FileAlreadyExistsException e) { + // ignore + } + } + + public PluginLogger getLogger() { + return new Log4jPluginLogger(LogManager.getLogger(AurionChatPlugin.NAME)); + } + +} diff --git a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/CommandExecutor.java b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/CommandExecutor.java new file mode 100644 index 0000000..15c1f05 --- /dev/null +++ b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/CommandExecutor.java @@ -0,0 +1,67 @@ +package com.mineaurion.aurionchat.sponge; + +import com.mineaurion.aurionchat.common.command.CommandManager; +import com.mineaurion.aurionchat.common.misc.ArgumentTokenizer; +import net.kyori.adventure.text.Component; +import org.spongepowered.api.command.Command; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.command.CommandCompletion; +import org.spongepowered.api.command.CommandResult; +import org.spongepowered.api.command.exception.CommandException; +import org.spongepowered.api.command.parameter.ArgumentReader; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class CommandExecutor extends CommandManager implements Command.Raw { + private final AurionChat plugin; + + public CommandExecutor(AurionChat plugin){ + super(plugin); + this.plugin = plugin; + } + + @Override + public CommandResult process(CommandCause source, ArgumentReader.Mutable arguments) throws CommandException { + //TODO: warning workaround maybe not ok for passing arguments to executeCommand + executeCommand( + this.plugin.getSenderFactory().wrap(source.audience()), + "aurionchat", + Arrays.asList(arguments.input().split(" ")) + ); + return CommandResult.success(); + } + + @Override + public List complete(CommandCause source, ArgumentReader.Mutable arguments) throws CommandException { + return tabCompleteCommand( + plugin.getSenderFactory().wrap(source.audience()), + ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(arguments.input()) + ).stream() + .map(CommandCompletion::of) + .collect(Collectors.toList()) + ; + } + + @Override + public boolean canExecute(CommandCause cause) { + return true; // check permission internally + } + + @Override + public Optional shortDescription(CommandCause cause) { + return Optional.of(Component.text("Use & Manage Aurionchat")); + } + + @Override + public Optional extendedDescription(CommandCause cause) { + return Optional.empty(); + } + + @Override + public Component usage(CommandCause cause) { + return Component.text("/chat"); + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/PlayerFactory.java b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/PlayerFactory.java deleted file mode 100644 index a18cb48..0000000 --- a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/PlayerFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.mineaurion.aurionchat.sponge; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import org.spongepowered.api.entity.living.player.server.ServerPlayer; - -import java.util.UUID; - -public class PlayerFactory extends com.mineaurion.aurionchat.common.player.PlayerFactory { - - public PlayerFactory(){ - super(true); - } - - @Override - protected UUID getUUID(ServerPlayer player) { - return player.uniqueId(); - } - - @Override - protected String getName(ServerPlayer player) { - return PlainTextComponentSerializer.plainText().serialize(player.displayName().get()); - } - - @Override - protected void sendMessage(ServerPlayer player, Component message) { - player.sendMessage(message); - } - - @Override - protected boolean hasPermission(ServerPlayer player, String permission) { - return player.hasPermission(permission); - } -} diff --git a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/SenderFactory.java b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/SenderFactory.java new file mode 100644 index 0000000..6868439 --- /dev/null +++ b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/SenderFactory.java @@ -0,0 +1,60 @@ +package com.mineaurion.aurionchat.sponge; + +import com.mineaurion.aurionchat.api.model.ServerPlayer; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import org.spongepowered.api.SystemSubject; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.service.permission.Subject; + +import java.util.UUID; + +public class SenderFactory extends com.mineaurion.aurionchat.common.command.sender.SenderFactory { + + public SenderFactory(AurionChat plugin){ + super(plugin, true); + } + + @Override + protected String getName(Audience source) { + if(source instanceof Player){ + return ((Player) source).name(); + } + return ServerPlayer.CONSOLE_NAME; + } + + // TODO: review this, it's copy/paste from getName + @Override + protected String getDisplayName(Audience sender) { + if(sender instanceof Player){ + return ((Player) sender).name(); + } + return ServerPlayer.CONSOLE_NAME; + } + + @Override + protected UUID getId(Audience source) { + if(source instanceof Player){ + return ((Player) source).uniqueId(); + } + return ServerPlayer.CONSOLE_UUID; + } + + @Override + protected void sendMessage(Audience source, Component message) { + source.sendMessage(message); + } + + @Override + protected boolean hasPermission(Audience source, String permission) { + if(!(source instanceof Subject)){ + throw new IllegalStateException("Source is not a subject"); + } + return ((Subject) source).hasPermission(permission); + } + + @Override + protected boolean isConsole(Audience sender) { + return sender instanceof SystemSubject; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/command/ChatCommand.java b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/command/ChatCommand.java deleted file mode 100644 index 325c6b3..0000000 --- a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/command/ChatCommand.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.mineaurion.aurionchat.sponge.command; - -import com.mineaurion.aurionchat.common.AurionChatPlayer; -import com.mineaurion.aurionchat.common.command.ChatCommandCommon; -import com.mineaurion.aurionchat.sponge.AurionChat; -import net.kyori.adventure.text.Component; -import org.spongepowered.api.command.Command; -import org.spongepowered.api.command.CommandResult; -import org.spongepowered.api.command.parameter.CommandContext; -import org.spongepowered.api.command.parameter.Parameter; -import org.spongepowered.api.entity.living.player.server.ServerPlayer; -import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; -import org.spongepowered.plugin.PluginContainer; - -public class ChatCommand extends ChatCommandCommon { - Parameter.Value channel = Parameter.string().key("channel").build(); - - public ChatCommand(AurionChat plugin, RegisterCommandEvent commandManager){ - super(plugin); - commandManager.register(plugin.container, register(), "channel", "ch"); - registerAliasChannels(commandManager, plugin.container); - } - - private Command.Parameterized register(){ - Command.Parameterized join = Command.builder() - .permission("aurionchat.command.channel") - .addParameter(channel) - .executor(ctx -> this.execute(ctx, Action.JOIN)) - .build(); - - Command.Parameterized leave = Command.builder() - .permission("aurionchat.command.channel") - .addParameter(channel) - .executor(ctx -> this.execute(ctx, Action.LEAVE)) - .build(); - - Command.Parameterized spy = Command.builder() - .permission("aurionchat.command.channel") - .addParameter(channel) - .executor(ctx -> this.execute(ctx, Action.SPY)) - .build(); - - Command.Parameterized allListen = Command.builder() - .permission("aurionchat.command.channel") - .executor(ctx -> this.execute(ctx, Action.ALLLISTEN)) - .build(); - - Command.Parameterized reload = Command.builder() - .permission("aurionchat.reload") - .shortDescription(Component.text("AurionChat's reload command")) - .executor(ctx -> this.execute(ctx, Action.RELOAD)) - .build(); - - return Command.builder() - .shortDescription(Component.text("AurionChat's command to manage chat channels")) - .executionRequirements(context -> context.cause().root() instanceof ServerPlayer) - .addChild(join, "join") - .addChild(leave, "leave") - .addChild(spy, "spy") - .addChild(allListen, "allListen") - .addChild(reload, "reload") - .executor(ctx -> this.execute(ctx, Action.DEFAULT)) - .build(); - } - - private void registerAliasChannels(RegisterCommandEvent commandManager, PluginContainer container){ - Parameter.Value messageParameter = Parameter.remainingJoinedStrings().optional().key("message").build(); - getPlugin().getConfigurationAdapter().getChannels().forEach((name, channel) -> { - Command.Parameterized aliasCmd = Command.builder() - .shortDescription(Component.text("AurionChat's command to send message on specific channel")) - .addParameter(Parameter.remainingJoinedStrings().optional().key("message").build()) - .executionRequirements(context -> context.cause().root() instanceof ServerPlayer) - .executor(ctx -> { - ServerPlayer player = (ServerPlayer) ctx.cause().root(); - return onCommand( - getPlugin().getAurionChatPlayers().get(player.uniqueId()), - Component.text(ctx.requireOne(messageParameter)), - name, - channel.format - ) ? CommandResult.success() :CommandResult.error(Component.text("Error when executing the command")); - }).build(); - commandManager.register(container, aliasCmd, channel.alias, name); - }); - } - - public CommandResult execute(CommandContext context, Action action) { - String channel = context.requireOne(this.channel); - ServerPlayer player = (ServerPlayer) context.cause().root(); - AurionChatPlayer aurionChatPlayer = getPlugin().getAurionChatPlayers().get(player.uniqueId()); - return this.execute(aurionChatPlayer, channel, action) ? CommandResult.success() : CommandResult.error(Component.text("Error when executing the command")); - } -} diff --git a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java index 89c4877..eae7586 100644 --- a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java +++ b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/ChatListener.java @@ -47,7 +47,7 @@ public void onPlayerChat(PlayerChatEvent event, @First ServerPlayer player) { try { plugin.getChatService().send(packet); } catch (Exception e) { - this.plugin.getlogger().severe(e.getMessage()); + this.plugin.getLogger().severe(e.getMessage()); } event.setCancelled(true); // TODO: need to remove that. Need to adapt rabbitmq to a fanout exchange for the chat. } diff --git a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/LoginListener.java b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/LoginListener.java index 2828d6f..88aeeb6 100644 --- a/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/LoginListener.java +++ b/sponge/src/main/java/com/mineaurion/aurionchat/sponge/listeners/LoginListener.java @@ -15,16 +15,16 @@ public LoginListener(AurionChat plugin){ @Listener public void onPlayerKick(KickPlayerEvent event) { - playerLeaving(plugin.getPlayerFactory().wrap(event.player())); + playerLeaving(plugin.getSenderFactory().wrap(event.player())); } @Listener public void onPlayerQuit(ServerSideConnectionEvent.Login event) { - Sponge.server().player(event.profile().uniqueId()).ifPresent(serverPlayer -> playerLeaving(plugin.getPlayerFactory().wrap(serverPlayer))); + Sponge.server().player(event.profile().uniqueId()).ifPresent(serverPlayer -> playerLeaving(plugin.getSenderFactory().wrap(serverPlayer))); } @Listener public void onPlayerJoin(ServerSideConnectionEvent.Disconnect event) { - playerJoin(plugin.getPlayerFactory().wrap(event.player())); + playerJoin(plugin.getSenderFactory().wrap(event.player())); } }