In-game commands

This commit is contained in:
Vankka 2022-03-20 13:46:06 +02:00
parent 90150325bf
commit 99a87382bd
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
68 changed files with 2874 additions and 327 deletions

View File

@ -35,7 +35,7 @@ import net.dv8tion.jda.api.JDA;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Optional; import java.util.*;
/** /**
* The DiscordSRV API. * The DiscordSRV API.
@ -215,4 +215,27 @@ public interface DiscordSRVApi {
} }
} }
enum ReloadFlag {
CONFIG(false),
LINKED_ACCOUNT_PROVIDER(false),
STORAGE(true),
DISCORD_CONNECTION(true),
MODULES(false),
;
public static final Set<ReloadFlag> ALL = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(values())));
public static final Set<ReloadFlag> DEFAULT_FLAGS = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(CONFIG, MODULES)));
private final boolean requiresConfirm;
ReloadFlag(boolean requiresConfirm) {
this.requiresConfirm = requiresConfirm;
}
public boolean requiresConfirm() {
return requiresConfirm;
}
}
} }

View File

@ -25,15 +25,9 @@ package com.discordsrv.api.event.events.lifecycle;
import com.discordsrv.api.event.events.Event; import com.discordsrv.api.event.events.Event;
public class DiscordSRVReloadEvent implements Event { /**
* Indicates that DiscordSRV's Discord connection has been (re-)established. This may run more than once.
*/
public class DiscordSRVConnectedEvent implements Event {
private final boolean config;
public DiscordSRVReloadEvent(boolean config) {
this.config = config;
}
public boolean isConfig() {
return config;
}
} }

View File

@ -26,8 +26,7 @@ package com.discordsrv.api.event.events.lifecycle;
import com.discordsrv.api.event.events.Event; import com.discordsrv.api.event.events.Event;
/** /**
* Indicates that DiscordSRV's systems (including Discord connection) are ready. * Indicates that DiscordSRV is ready (including Discord connection), this only runs once when DiscordSRV enables.
*/ */
public class DiscordSRVReadyEvent implements Event { public class DiscordSRVReadyEvent implements Event {
} }

View File

@ -0,0 +1,49 @@
/*
* This file is part of the DiscordSRV API, licensed under the MIT License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.discordsrv.api.event.events.lifecycle;
import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.api.event.events.Event;
import java.util.Set;
/**
* An event for when DiscordSRV successfully reloads partially or completely.
*/
public class DiscordSRVReloadedEvent implements Event {
private final Set<DiscordSRVApi.ReloadFlag> flags;
public DiscordSRVReloadedEvent(Set<DiscordSRVApi.ReloadFlag> flags) {
this.flags = flags;
}
/**
* Set of DiscordSRV systems that were reloaded.
* @return an unmodifiable set of {@link com.discordsrv.api.DiscordSRVApi.ReloadFlag}s
*/
public Set<DiscordSRVApi.ReloadFlag> flags() {
return flags;
}
}

View File

@ -1,6 +1,6 @@
plugins { plugins {
id 'com.github.johnrengelman.shadow' version '7.1.1' apply false id 'com.github.johnrengelman.shadow' version '7.1.1' apply false
id 'org.cadixdev.licenser' version '0.6.0' apply false id 'org.cadixdev.licenser' version '0.6.1' apply false
id 'net.kyori.blossom' version '1.2.0' apply false id 'net.kyori.blossom' version '1.2.0' apply false
id 'dev.vankka.dependencydownload.plugin' version '1.1.5-SNAPSHOT' apply false id 'dev.vankka.dependencydownload.plugin' version '1.1.5-SNAPSHOT' apply false
} }
@ -17,8 +17,8 @@ ext {
// Configurate // Configurate
configurateVersion = '4.1.2' configurateVersion = '4.1.2'
// Adventure & Adventure Platform // Adventure & Adventure Platform
adventureVersion = '4.9.1' adventureVersion = '4.10.0'
adventurePlatformVersion = '4.0.0' adventurePlatformVersion = '4.1.0'
} }
allprojects { allprojects {
@ -59,6 +59,12 @@ allprojects {
includeGroup 'me.scarsz' includeGroup 'me.scarsz'
} }
} }
maven {
url 'https://libraries.minecraft.net'
content {
includeGroup 'com.mojang'
}
}
// Get dependencies from central last, everything else should be filtered // Get dependencies from central last, everything else should be filtered
mavenCentral() mavenCentral()

View File

@ -1,5 +1,30 @@
import dev.vankka.dependencydownload.task.GenerateDependencyDownloadResourceTask
apply from: rootProject.file('buildscript/runtime.gradle') apply from: rootProject.file('buildscript/runtime.gradle')
configurations {
commodore
compileOnly.extendsFrom commodore
}
task generateResourceForCommodore(type: GenerateDependencyDownloadResourceTask) {
var conf = configurations.commodore
configuration = conf
file = 'dependencies/' + conf.name + '.txt'
}
shadowJar {
archiveFileName = 'bukkit.jarinjar'
[
'net.kyori',
'me.lucko.commodore'
].each {
relocate it, 'com.discordsrv.dependencies.' + it
}
// More relocations in buildscript/relocations.gradle
}
allprojects { allprojects {
repositories { repositories {
exclusiveContent { exclusiveContent {
@ -41,13 +66,18 @@ dependencies {
// Adventure // Adventure
runtimeDownloadApi 'net.kyori:adventure-platform-bukkit:' + rootProject.adventurePlatformVersion runtimeDownloadApi 'net.kyori:adventure-platform-bukkit:' + rootProject.adventurePlatformVersion
// Commodore
commodore('me.lucko:commodore:1.10') {
// We only use commodore when it's included in the server, so we don't want to download it
exclude module: 'brigadier'
// We don't use the commodore file format
exclude module: 'commodore-file'
}
// Integrations // Integrations
compileOnly 'net.milkbowl.vault:VaultAPI:1.7' compileOnly 'net.milkbowl.vault:VaultAPI:1.7'
} }
shadowJar { processResources {
archiveFileName = 'bukkit.jarinjar' dependsOn(generateResourceForCommodore)
relocate 'net.kyori', 'com.discordsrv.dependencies.net.kyori'
// More relocations in buildscript/relocations.gradle
} }

View File

@ -5,3 +5,14 @@ description: ""
authors: [Scarsz, Vankka] authors: [Scarsz, Vankka]
load: STARTUP load: STARTUP
api-version: 1.13 api-version: 1.13
softdepend: [
# Permission + group providers
Vault, LuckPerms,
# Adventure
ViaVersion
]
commands:
discordsrv:
description: "DiscordSRV's primary command"

View File

@ -19,6 +19,7 @@
package com.discordsrv.bukkit; package com.discordsrv.bukkit;
import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.bukkit.command.game.handler.AbstractBukkitCommandHandler;
import com.discordsrv.bukkit.config.connection.BukkitConnectionConfig; import com.discordsrv.bukkit.config.connection.BukkitConnectionConfig;
import com.discordsrv.bukkit.config.main.BukkitConfig; import com.discordsrv.bukkit.config.main.BukkitConfig;
import com.discordsrv.bukkit.config.manager.BukkitConfigManager; import com.discordsrv.bukkit.config.manager.BukkitConfigManager;
@ -32,6 +33,7 @@ import com.discordsrv.bukkit.listener.BukkitStatusMessageListener;
import com.discordsrv.bukkit.player.BukkitPlayerProvider; import com.discordsrv.bukkit.player.BukkitPlayerProvider;
import com.discordsrv.bukkit.plugin.BukkitPluginManager; import com.discordsrv.bukkit.plugin.BukkitPluginManager;
import com.discordsrv.bukkit.scheduler.BukkitScheduler; import com.discordsrv.bukkit.scheduler.BukkitScheduler;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.manager.ConnectionConfigManager; import com.discordsrv.common.config.manager.ConnectionConfigManager;
import com.discordsrv.common.config.manager.MainConfigManager; import com.discordsrv.common.config.manager.MainConfigManager;
import com.discordsrv.common.debug.data.OnlineMode; import com.discordsrv.common.debug.data.OnlineMode;
@ -60,6 +62,7 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConne
private final BukkitConsole console; private final BukkitConsole console;
private final BukkitPlayerProvider playerProvider; private final BukkitPlayerProvider playerProvider;
private final BukkitPluginManager pluginManager; private final BukkitPluginManager pluginManager;
private AbstractBukkitCommandHandler commandHandler;
private final BukkitConnectionConfigManager connectionConfigManager; private final BukkitConnectionConfigManager connectionConfigManager;
private final BukkitConfigManager configManager; private final BukkitConfigManager configManager;
@ -157,6 +160,11 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConne
return bootstrap.getClasspathAppender(); return bootstrap.getClasspathAppender();
} }
@Override
public ICommandHandler commandHandler() {
return commandHandler;
}
@Override @Override
public ConnectionConfigManager<BukkitConnectionConfig> connectionConfigManager() { public ConnectionConfigManager<BukkitConnectionConfig> connectionConfigManager() {
return connectionConfigManager; return connectionConfigManager;
@ -176,15 +184,19 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConne
this.audiences = BukkitAudiences.create(bootstrap.getPlugin()); this.audiences = BukkitAudiences.create(bootstrap.getPlugin());
server().getPluginManager().registerEvents(new BukkitConnectionListener(this), plugin()); server().getPluginManager().registerEvents(new BukkitConnectionListener(this), plugin());
super.enable(); // Command handler
commandHandler = AbstractBukkitCommandHandler.get(this);
// Register listeners // Register listeners
server().getPluginManager().registerEvents(BukkitChatListener.get(this), plugin()); server().getPluginManager().registerEvents(BukkitChatListener.get(this), plugin());
server().getPluginManager().registerEvents(new BukkitDeathListener(this), plugin()); server().getPluginManager().registerEvents(new BukkitDeathListener(this), plugin());
server().getPluginManager().registerEvents(new BukkitStatusMessageListener(this), plugin()); server().getPluginManager().registerEvents(new BukkitStatusMessageListener(this), plugin());
// Modules
registerModule(VaultIntegration::new); registerModule(VaultIntegration::new);
registerModule(MinecraftToDiscordChatModule::new); registerModule(MinecraftToDiscordChatModule::new);
super.enable();
} }
} }

View File

@ -26,6 +26,8 @@ import dev.vankka.mcdependencydownload.classloader.JarInJarClassLoader;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class DiscordSRVBukkitBootstrap extends BukkitBootstrap { public class DiscordSRVBukkitBootstrap extends BukkitBootstrap {
@ -40,11 +42,25 @@ public class DiscordSRVBukkitBootstrap extends BukkitBootstrap {
this.dependencies = new InitialDependencyLoader( this.dependencies = new InitialDependencyLoader(
logger, logger,
plugin.getDataFolder().toPath(), plugin.getDataFolder().toPath(),
new String[] {"dependencies/runtimeDownload-bukkit.txt"}, getDependencyResources(),
getClasspathAppender() getClasspathAppender()
); );
} }
private static String[] getDependencyResources() {
List<String> resources = new ArrayList<>();
resources.add("dependencies/runtimeDownload-bukkit.txt");
try {
Class.forName("com.mojang.brigadier.CommandDispatcher");
resources.add("dependencies/commodore.txt");
} catch (ClassNotFoundException ignored) {
// CommandDispatches not present, don't need to bother downloading commodore
}
return resources.toArray(new String[0]);
}
@Override @Override
public void onEnable() { public void onEnable() {
dependencies.loadAndEnable(() -> this.discordSRV = new BukkitDiscordSRV(this, logger)); dependencies.loadAndEnable(() -> this.discordSRV = new BukkitDiscordSRV(this, logger));

View File

@ -0,0 +1,49 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.command.game.handler;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.common.command.game.handler.BasicCommandHandler;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public abstract class AbstractBukkitCommandExecutor extends AbstractBukkitCommandHandler implements CommandExecutor {
protected final BasicCommandHandler handler = new BasicCommandHandler();
public AbstractBukkitCommandExecutor(BukkitDiscordSRV discordSRV) {
super(discordSRV);
}
@Override
public boolean onCommand(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args
) {
handler.execute(sender(sender), label, Arrays.asList(args));
return true;
}
}

View File

@ -0,0 +1,91 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.command.game.handler;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.command.game.sender.BukkitCommandSender;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.NamedLogger;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Constructor;
import java.util.Locale;
public abstract class AbstractBukkitCommandHandler implements ICommandHandler {
public static AbstractBukkitCommandHandler get(BukkitDiscordSRV discordSRV) {
try {
Class.forName("me.lucko.commodore.Commodore");
return new CommodoreHandler(discordSRV);
} catch (Throwable e) {
BukkitBasicCommandHandler handler = new BukkitBasicCommandHandler(discordSRV);
if (e instanceof ClassNotFoundException) {
handler.logger.debug("Brigadier classes not present, not using commodore");
} else {
handler.logger.debug("Failed to initialize Commodore", e);
}
return handler;
}
}
protected final BukkitDiscordSRV discordSRV;
protected final Logger logger;
public AbstractBukkitCommandHandler(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
this.logger = new NamedLogger(discordSRV, "COMMAND_HANDLER");
}
protected ICommandSender sender(CommandSender commandSender) {
if (commandSender instanceof Player) {
return discordSRV.playerProvider().player((Player) commandSender);
} else if (commandSender instanceof ConsoleCommandSender) {
return discordSRV.console();
} else {
return new BukkitCommandSender(discordSRV, commandSender, () -> discordSRV.audiences().sender(commandSender));
}
}
protected PluginCommand command(GameCommand gameCommand) {
String label = gameCommand.getLabel();
PluginCommand pluginCommand = discordSRV.plugin().getCommand(label);
if (pluginCommand != null) {
return pluginCommand;
}
PluginCommand command = null;
try {
Constructor<?> constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
command = (PluginCommand) constructor.newInstance(label, discordSRV.plugin());
discordSRV.server().getCommandMap().register(label, discordSRV.plugin().getName().toLowerCase(Locale.ROOT), command);
} catch (ReflectiveOperationException ignored) {}
return command;
}
}

View File

@ -0,0 +1,63 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.command.game.handler;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
public class BukkitBasicCommandHandler extends AbstractBukkitCommandExecutor implements TabCompleter {
public BukkitBasicCommandHandler(BukkitDiscordSRV discordSRV) {
super(discordSRV);
}
@Override
public @Nullable List<String> onTabComplete(
@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String alias,
@NotNull String[] args
) {
return handler.suggest(sender(sender), alias, Arrays.asList(args));
}
@Override
public void registerCommand(GameCommand command) {
PluginCommand pluginCommand = command(command);
if (pluginCommand == null) {
logger.error("Failed to create command " + command.getLabel());
return;
}
logger.debug("Registering command " + command.getLabel() + " with basic handler");
handler.registerCommand(command);
pluginCommand.setExecutor(this);
pluginCommand.setTabCompleter(this);
}
}

View File

@ -0,0 +1,73 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.command.game.handler;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.handler.util.BrigadierUtil;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.command.PluginCommand;
/**
* No avoiding basic handler on bukkit. Unfortunately it isn't possible to use brigadier for executing.
*/
public class CommodoreHandler extends AbstractBukkitCommandExecutor {
private final Commodore commodore;
public CommodoreHandler(BukkitDiscordSRV discordSRV) {
super(discordSRV);
this.commodore = CommodoreProvider.getCommodore(discordSRV.plugin());
}
@Override
public void registerCommand(GameCommand command) {
logger.debug("Registering command " + command.getLabel() + " with commodore");
LiteralCommandNode<?> argumentBuilder = BrigadierUtil.convertToBrigadier(
command,
wrapper -> sender(commodore.getBukkitSender(wrapper))
);
CommandNode<?> redirection = argumentBuilder.getRedirect();
if (redirection != null) {
// Commodore handles the label being different fine
argumentBuilder = (LiteralCommandNode<?>) redirection;
}
PluginCommand pluginCommand = command(command);
if (pluginCommand == null) {
logger.error("Failed to create command " + command.getLabel());
return;
}
handler.registerCommand(command);
pluginCommand.setExecutor(this);
String requiredPermission = command.getRequiredPermission();
LiteralCommandNode<?> finalArgumentBuilder = argumentBuilder;
discordSRV.scheduler().runOnMainThread(() -> commodore.register(
pluginCommand,
finalArgumentBuilder,
player -> requiredPermission == null || player.hasPermission(requiredPermission)
));
}
}

View File

@ -0,0 +1,55 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bukkit.command.game.sender;
import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.audience.Audience;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public class BukkitCommandSender implements ICommandSender {
protected final BukkitDiscordSRV discordSRV;
protected final CommandSender commandSender;
protected final Supplier<Audience> audienceSupplier;
public BukkitCommandSender(BukkitDiscordSRV discordSRV, CommandSender commandSender, Supplier<Audience> audienceSupplier) {
this.discordSRV = discordSRV;
this.commandSender = commandSender;
this.audienceSupplier = audienceSupplier;
}
@Override
public boolean hasPermission(String permission) {
return commandSender.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.scheduler().runOnMainThread(() -> discordSRV.server().dispatchCommand(commandSender, command));
}
@Override
public @NotNull Audience audience() {
return audienceSupplier.get();
}
}

View File

@ -19,21 +19,19 @@
package com.discordsrv.bukkit.console; package com.discordsrv.bukkit.console;
import com.discordsrv.bukkit.BukkitDiscordSRV; import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.command.game.sender.BukkitCommandSender;
import com.discordsrv.common.console.Console; import com.discordsrv.common.console.Console;
import com.discordsrv.common.logging.NamedLogger; import com.discordsrv.common.logging.NamedLogger;
import com.discordsrv.common.logging.backend.LoggingBackend; import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.backend.impl.JavaLoggerImpl; import com.discordsrv.common.logging.backend.impl.JavaLoggerImpl;
import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl; import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
public class BukkitConsole implements Console { public class BukkitConsole extends BukkitCommandSender implements Console {
private final BukkitDiscordSRV discordSRV;
private final LoggingBackend loggingBackend; private final LoggingBackend loggingBackend;
public BukkitConsole(BukkitDiscordSRV discordSRV) { public BukkitConsole(BukkitDiscordSRV discordSRV) {
this.discordSRV = discordSRV; super(discordSRV, discordSRV.server().getConsoleSender(), () -> discordSRV.audiences().console());
LoggingBackend logging; LoggingBackend logging;
try { try {
@ -49,25 +47,8 @@ public class BukkitConsole implements Console {
this.loggingBackend = logging; this.loggingBackend = logging;
} }
@Override
public boolean hasPermission(String permission) {
return discordSRV.server().getConsoleSender().hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.scheduler().runOnMainThread(() ->
discordSRV.server().dispatchCommand(
discordSRV.server().getConsoleSender(), command));
}
@Override @Override
public LoggingBackend loggingBackend() { public LoggingBackend loggingBackend() {
return loggingBackend; return loggingBackend;
} }
@Override
public @NotNull Audience audience() {
return discordSRV.audiences().console();
}
} }

View File

@ -42,7 +42,6 @@ public class BukkitOfflinePlayer implements IOfflinePlayer {
return discordSRV; return discordSRV;
} }
@SuppressWarnings("NullabilityProblems")
@Override @Override
public String username() { public String username() {
return offlinePlayer.getName(); return offlinePlayer.getName();

View File

@ -19,36 +19,25 @@
package com.discordsrv.bukkit.player; package com.discordsrv.bukkit.player;
import com.discordsrv.bukkit.BukkitDiscordSRV; import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.bukkit.command.game.sender.BukkitCommandSender;
import com.discordsrv.bukkit.component.util.PaperComponentUtil; import com.discordsrv.bukkit.component.util.PaperComponentUtil;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil; import com.discordsrv.common.component.util.ComponentUtil;
import com.discordsrv.common.player.IPlayer; import com.discordsrv.common.player.IPlayer;
import net.kyori.adventure.audience.Audience; import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@SuppressWarnings("NullableProblems") // BukkitOfflinePlayer nullability public class BukkitPlayer extends BukkitCommandSender implements IPlayer {
public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
private final Player player; private final Player player;
private final Audience audience; private final Identity identity;
public BukkitPlayer(BukkitDiscordSRV discordSRV, Player player) { public BukkitPlayer(BukkitDiscordSRV discordSRV, Player player) {
super(discordSRV, player); super(discordSRV, player, () -> discordSRV.audiences().player(player));
this.player = player; this.player = player;
this.audience = discordSRV.audiences().player(player); this.identity = Identity.identity(player.getUniqueId());
}
@Override
public boolean hasPermission(String permission) {
return player.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.scheduler().runOnMainThread(() ->
discordSRV.server().dispatchCommand(player, command));
} }
@Override @Override
@ -56,6 +45,11 @@ public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
return discordSRV; return discordSRV;
} }
@Override
public @NotNull String username() {
return player.getName();
}
@SuppressWarnings("deprecation") // Paper @SuppressWarnings("deprecation") // Paper
@Override @Override
public @NotNull Component displayName() { public @NotNull Component displayName() {
@ -65,7 +59,7 @@ public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
} }
@Override @Override
public @NotNull Audience audience() { public @NotNull Identity identity() {
return audience; return identity;
} }
} }

View File

@ -26,7 +26,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Optional; import java.util.Optional;
@ -52,8 +52,8 @@ public class BukkitPlayerProvider extends ServerPlayerProvider<BukkitPlayer, Buk
} }
} }
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerLogin(PlayerLoginEvent event) {
addPlayer(event.getPlayer(), false); addPlayer(event.getPlayer(), false);
} }

View File

@ -1,5 +1,16 @@
apply from: rootProject.file('buildscript/runtime.gradle') apply from: rootProject.file('buildscript/runtime.gradle')
shadowJar {
archiveFileName = 'bungee.jarinjar'
[
'net.kyori'
].each {
relocate it, 'com.discordsrv.dependencies.' + it
}
// More relocations in buildscript/relocations.gradle
}
allprojects { allprojects {
repositories { repositories {
exclusiveContent { exclusiveContent {
@ -32,10 +43,3 @@ dependencies {
runtimeDownloadApi 'net.kyori:adventure-platform-bungeecord:' + rootProject.adventurePlatformVersion runtimeDownloadApi 'net.kyori:adventure-platform-bungeecord:' + rootProject.adventurePlatformVersion
runtimeDownloadApi 'net.kyori:adventure-text-serializer-bungeecord:' + rootProject.adventurePlatformVersion runtimeDownloadApi 'net.kyori:adventure-text-serializer-bungeecord:' + rootProject.adventurePlatformVersion
} }
shadowJar {
archiveFileName = 'bungee.jarinjar'
relocate 'net.kyori', 'com.discordsrv.dependencies.net.kyori'
// More relocations in buildscript/relocations.gradle
}

View File

@ -18,9 +18,11 @@
package com.discordsrv.bungee; package com.discordsrv.bungee;
import com.discordsrv.bungee.command.game.handler.BungeeCommandHandler;
import com.discordsrv.bungee.console.BungeeConsole; import com.discordsrv.bungee.console.BungeeConsole;
import com.discordsrv.bungee.player.BungeePlayerProvider; import com.discordsrv.bungee.player.BungeePlayerProvider;
import com.discordsrv.bungee.plugin.BungeePluginManager; import com.discordsrv.bungee.plugin.BungeePluginManager;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.connection.ConnectionConfig; import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig; import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager; import com.discordsrv.common.config.manager.ConnectionConfigManager;
@ -49,6 +51,7 @@ public class BungeeDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionConf
private final BungeeConsole console; private final BungeeConsole console;
private final BungeePlayerProvider playerProvider; private final BungeePlayerProvider playerProvider;
private final BungeePluginManager pluginManager; private final BungeePluginManager pluginManager;
private BungeeCommandHandler commandHandler;
public BungeeDiscordSRV(DiscordSRVBungeeBootstrap bootstrap, Logger logger) { public BungeeDiscordSRV(DiscordSRVBungeeBootstrap bootstrap, Logger logger) {
this.bootstrap = bootstrap; this.bootstrap = bootstrap;
@ -120,6 +123,11 @@ public class BungeeDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionConf
return bootstrap.getClasspathAppender(); return bootstrap.getClasspathAppender();
} }
@Override
public ICommandHandler commandHandler() {
return commandHandler;
}
@Override @Override
public ConnectionConfigManager<ConnectionConfig> connectionConfigManager() { public ConnectionConfigManager<ConnectionConfig> connectionConfigManager() {
return null; return null;
@ -135,6 +143,8 @@ public class BungeeDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionConf
// Player related // Player related
this.audiences = BungeeAudiences.create(bootstrap.getPlugin()); this.audiences = BungeeAudiences.create(bootstrap.getPlugin());
this.commandHandler = new BungeeCommandHandler(this);
super.enable(); super.enable();
} }
} }

View File

@ -0,0 +1,80 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bungee.command.game.handler;
import com.discordsrv.bungee.BungeeDiscordSRV;
import com.discordsrv.bungee.command.game.sender.BungeeCommandSender;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.handler.BasicCommandHandler;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor;
import java.util.Arrays;
/**
* BungeeCord has not concept of Brigadier.
*/
public class BungeeCommandHandler extends BasicCommandHandler {
private final BungeeDiscordSRV discordSRV;
public BungeeCommandHandler(BungeeDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void registerCommand(GameCommand command) {
super.registerCommand(command);
discordSRV.proxy().getPluginManager().registerCommand(discordSRV.plugin(), new BungeeCommand(command));
}
public ICommandSender getSender(CommandSender sender) {
if (sender instanceof ProxiedPlayer) {
return discordSRV.playerProvider().player((ProxiedPlayer) sender);
} else if (sender == discordSRV.proxy().getConsole()) {
return discordSRV.console();
} else {
return new BungeeCommandSender(discordSRV, sender, () -> discordSRV.audiences().sender(sender));
}
}
public class BungeeCommand extends Command implements TabExecutor {
private final GameCommand command;
public BungeeCommand(GameCommand command) {
super(command.getLabel(), command.getRequiredPermission());
this.command = command;
}
@Override
public void execute(CommandSender sender, String[] args) {
BungeeCommandHandler.this.execute(getSender(sender), command.getLabel(), Arrays.asList(args));
}
@Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
return BungeeCommandHandler.this.suggest(getSender(sender), command.getLabel(), Arrays.asList(args));
}
}
}

View File

@ -0,0 +1,55 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.bungee.command.game.sender;
import com.discordsrv.bungee.BungeeDiscordSRV;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.audience.Audience;
import net.md_5.bungee.api.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public class BungeeCommandSender implements ICommandSender {
protected final BungeeDiscordSRV discordSRV;
protected final CommandSender commandSender;
protected final Supplier<Audience> audience;
public BungeeCommandSender(BungeeDiscordSRV discordSRV, CommandSender commandSender, Supplier<Audience> audience) {
this.discordSRV = discordSRV;
this.commandSender = commandSender;
this.audience = audience;
}
@Override
public boolean hasPermission(String permission) {
return commandSender.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getPluginManager().dispatchCommand(commandSender, command);
}
@Override
public @NotNull Audience audience() {
return audience.get();
}
}

View File

@ -19,40 +19,22 @@
package com.discordsrv.bungee.console; package com.discordsrv.bungee.console;
import com.discordsrv.bungee.BungeeDiscordSRV; import com.discordsrv.bungee.BungeeDiscordSRV;
import com.discordsrv.bungee.command.game.sender.BungeeCommandSender;
import com.discordsrv.common.console.Console; import com.discordsrv.common.console.Console;
import com.discordsrv.common.logging.backend.LoggingBackend; import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.backend.impl.JavaLoggerImpl; import com.discordsrv.common.logging.backend.impl.JavaLoggerImpl;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
public class BungeeConsole implements Console { public class BungeeConsole extends BungeeCommandSender implements Console {
private final BungeeDiscordSRV discordSRV;
private final LoggingBackend loggingBackend; private final LoggingBackend loggingBackend;
public BungeeConsole(BungeeDiscordSRV discordSRV) { public BungeeConsole(BungeeDiscordSRV discordSRV) {
this.discordSRV = discordSRV; super(discordSRV, discordSRV.proxy().getConsole(), () -> discordSRV.audiences().console());
this.loggingBackend = JavaLoggerImpl.getRoot(); this.loggingBackend = JavaLoggerImpl.getRoot();
} }
@Override
public boolean hasPermission(String permission) {
return discordSRV.proxy().getConsole().hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getPluginManager().dispatchCommand(
discordSRV.proxy().getConsole(), command);
}
@Override @Override
public LoggingBackend loggingBackend() { public LoggingBackend loggingBackend() {
return loggingBackend; return loggingBackend;
} }
@Override
public @NotNull Audience audience() {
return discordSRV.audiences().console();
}
} }

View File

@ -19,37 +19,24 @@
package com.discordsrv.bungee.player; package com.discordsrv.bungee.player;
import com.discordsrv.bungee.BungeeDiscordSRV; import com.discordsrv.bungee.BungeeDiscordSRV;
import com.discordsrv.bungee.command.game.sender.BungeeCommandSender;
import com.discordsrv.bungee.component.util.BungeeComponentUtil; import com.discordsrv.bungee.component.util.BungeeComponentUtil;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.player.IPlayer; import com.discordsrv.common.player.IPlayer;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.identity.Identity; import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class BungeePlayer implements IPlayer { public class BungeePlayer extends BungeeCommandSender implements IPlayer {
private final BungeeDiscordSRV discordSRV;
private final ProxiedPlayer player; private final ProxiedPlayer player;
private final Identity identity; private final Identity identity;
private final Audience audience;
public BungeePlayer(BungeeDiscordSRV discordSRV, ProxiedPlayer player) { public BungeePlayer(BungeeDiscordSRV discordSRV, ProxiedPlayer player) {
this.discordSRV = discordSRV; super(discordSRV, player, () -> discordSRV.audiences().player(player));
this.player = player; this.player = player;
this.identity = Identity.identity(player.getUniqueId()); this.identity = Identity.identity(player.getUniqueId());
this.audience = discordSRV.audiences().player(player);
}
@Override
public boolean hasPermission(String permission) {
return player.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getPluginManager().dispatchCommand(player, command);
} }
@Override @Override
@ -59,7 +46,7 @@ public class BungeePlayer implements IPlayer {
@Override @Override
public @NotNull String username() { public @NotNull String username() {
return player.getName(); return commandSender.getName();
} }
@Override @Override
@ -72,8 +59,4 @@ public class BungeePlayer implements IPlayer {
return BungeeComponentUtil.fromLegacy(player.getDisplayName()); return BungeeComponentUtil.fromLegacy(player.getDisplayName());
} }
@Override
public @NotNull Audience audience() {
return audience;
}
} }

View File

@ -64,6 +64,9 @@ dependencies {
runtimeDownloadApi 'dev.vankka:mcdiscordreserializer:4.2.4-SNAPSHOT' runtimeDownloadApi 'dev.vankka:mcdiscordreserializer:4.2.4-SNAPSHOT'
runtimeDownloadApi 'dev.vankka:enhancedlegacytext:1.0.0-SNAPSHOT' runtimeDownloadApi 'dev.vankka:enhancedlegacytext:1.0.0-SNAPSHOT'
// Brigadier
compileOnlyApi 'com.mojang:brigadier:1.0.18'
// Database // Database
hikari('com.zaxxer:HikariCP:4.0.3') { exclude group: 'org.slf4j' } hikari('com.zaxxer:HikariCP:4.0.3') { exclude group: 'org.slf4j' }
h2Driver 'com.h2database:h2:2.1.210' h2Driver 'com.h2database:h2:2.1.210'

View File

@ -20,13 +20,16 @@ package com.discordsrv.common;
import com.discordsrv.api.discord.connection.DiscordConnectionDetails; import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
import com.discordsrv.api.event.bus.EventBus; import com.discordsrv.api.event.bus.EventBus;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent; import com.discordsrv.api.event.events.lifecycle.DiscordSRVConnectedEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReadyEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadedEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent; import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
import com.discordsrv.api.module.type.Module; import com.discordsrv.api.module.type.Module;
import com.discordsrv.common.api.util.ApiInstanceUtil; import com.discordsrv.common.api.util.ApiInstanceUtil;
import com.discordsrv.common.channel.ChannelConfigHelper; import com.discordsrv.common.channel.ChannelConfigHelper;
import com.discordsrv.common.channel.ChannelUpdaterModule; import com.discordsrv.common.channel.ChannelUpdaterModule;
import com.discordsrv.common.channel.GlobalChannelLookupModule; import com.discordsrv.common.channel.GlobalChannelLookupModule;
import com.discordsrv.common.command.game.GameCommandModule;
import com.discordsrv.common.component.ComponentFactory; import com.discordsrv.common.component.ComponentFactory;
import com.discordsrv.common.config.connection.ConnectionConfig; import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.LinkedAccountConfig; import com.discordsrv.common.config.main.LinkedAccountConfig;
@ -45,6 +48,7 @@ import com.discordsrv.common.function.CheckedFunction;
import com.discordsrv.common.function.CheckedRunnable; import com.discordsrv.common.function.CheckedRunnable;
import com.discordsrv.common.groupsync.GroupSyncModule; import com.discordsrv.common.groupsync.GroupSyncModule;
import com.discordsrv.common.integration.LuckPermsIntegration; import com.discordsrv.common.integration.LuckPermsIntegration;
import com.discordsrv.common.invite.DiscordInviteModule;
import com.discordsrv.common.linking.LinkProvider; import com.discordsrv.common.linking.LinkProvider;
import com.discordsrv.common.linking.impl.MemoryLinker; import com.discordsrv.common.linking.impl.MemoryLinker;
import com.discordsrv.common.linking.impl.StorageLinker; import com.discordsrv.common.linking.impl.StorageLinker;
@ -69,6 +73,7 @@ import org.jetbrains.annotations.NotNull;
import javax.annotation.OverridingMethodsMustInvokeSuper; import javax.annotation.OverridingMethodsMustInvokeSuper;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -96,11 +101,12 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
// DiscordSRV // DiscordSRV
private DiscordSRVLogger logger; private DiscordSRVLogger logger;
private ModuleManager moduleManager; private ModuleManager moduleManager;
private JDAConnectionManager discordConnectionManager;
private ChannelConfigHelper channelConfig; private ChannelConfigHelper channelConfig;
private Storage storage; private Storage storage;
private boolean hikariLoaded = false;
private LinkProvider linkProvider; private LinkProvider linkProvider;
private DiscordConnectionManager discordConnectionManager;
// Internal // Internal
private final ReentrantLock lifecycleLock = new ReentrantLock(); private final ReentrantLock lifecycleLock = new ReentrantLock();
@ -121,6 +127,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
this.componentFactory = new ComponentFactory(this); this.componentFactory = new ComponentFactory(this);
this.discordAPI = new DiscordAPIImpl(this); this.discordAPI = new DiscordAPIImpl(this);
this.discordConnectionDetails = new DiscordConnectionDetailsImpl(this); this.discordConnectionDetails = new DiscordConnectionDetailsImpl(this);
this.discordConnectionManager = new JDAConnectionManager(this);
this.channelConfig = new ChannelConfigHelper(this); this.channelConfig = new ChannelConfigHelper(this);
} }
@ -225,9 +232,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected <T extends DiscordSRV> void registerModule(CheckedFunction<T, AbstractModule<?>> function) { protected <T extends DiscordSRV> void registerModule(CheckedFunction<T, AbstractModule<?>> function) {
try { moduleManager.registerModule((T) this, function);
registerModule(function.apply((T) this));
} catch (Throwable ignored) {}
} }
@Override @Override
@ -249,6 +254,9 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
this.status.set(status); this.status.set(status);
this.status.notifyAll(); this.status.notifyAll();
} }
if (status == Status.CONNECTED) {
eventBus().publish(new DiscordSRVConnectedEvent());
}
} }
@Override @Override
@ -299,7 +307,11 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
@Override @Override
public final CompletableFuture<Void> invokeEnable() { public final CompletableFuture<Void> invokeEnable() {
return invokeLifecycle(this::enable, "Failed to enable", true); return invokeLifecycle(() -> {
this.enable();
waitForStatus(Status.CONNECTED);
eventBus().publish(new DiscordSRVReadyEvent());
}, "Failed to enable", true);
} }
@Override @Override
@ -308,8 +320,8 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
} }
@Override @Override
public final CompletableFuture<Void> invokeReload() { public final CompletableFuture<Void> invokeReload(Set<ReloadFlag> flags, boolean silent) {
return invoke(this::reload, "Failed to reload", false); return invoke(() -> reload(flags, silent), "Failed to reload", false);
} }
@OverridingMethodsMustInvokeSuper @OverridingMethodsMustInvokeSuper
@ -323,76 +335,16 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
// Logging // Logging
DependencyLoggerAdapter.setAppender(new DependencyLoggingHandler(this)); DependencyLoggerAdapter.setAppender(new DependencyLoggingHandler(this));
// Config // Register PlayerProvider listeners
try { playerProvider().subscribe();
connectionConfigManager().load();
configManager().load();
eventBus().publish(new DiscordSRVReloadEvent(true));
} catch (Throwable t) {
setStatus(Status.FAILED_TO_LOAD_CONFIG);
throw t;
}
// Link provider
LinkedAccountConfig linkedAccountConfig = config().linkedAccounts;
if (linkedAccountConfig != null && linkedAccountConfig.enabled) {
String provider = linkedAccountConfig.provider;
switch (provider) {
case "auto":
case "storage":
linkProvider = new StorageLinker(this);
break;
case "memory": {
linkProvider = new MemoryLinker();
logger().warning("Using memory for linked accounts");
logger().warning("Linked accounts will be lost upon restart");
break;
}
default: {
logger().error("Unknown linked account provider: \"" + provider + "\", linked accounts will not be used");
break;
}
}
} else {
logger().info("Linked accounts are disabled");
}
// Storage
try {
try {
StorageType storageType = getStorageType();
logger().info("Using " + storageType.prettyName() + " as storage");
if (storageType.hikari()) {
DependencyLoader.hikari(this).process(classpathAppender()).get();
}
storage = storageType.storageFunction().apply(this);
storage.initialize();
logger().info("Storage connection successfully established");
} catch (ExecutionException e) {
throw new StorageException(e.getCause());
} catch (StorageException e) {
throw e;
} catch (Throwable t) {
throw new StorageException(t);
}
} catch (StorageException e) {
e.log(this);
logger().error("Failed to connect to storage");
setStatus(Status.FAILED_TO_START);
return;
}
discordConnectionManager = new JDAConnectionManager(this);
discordConnectionManager.connect().join();
// Placeholder result stringifiers & global contexts // Placeholder result stringifiers & global contexts
placeholderService().addResultMapper(new ComponentResultStringifier(this)); placeholderService().addResultMapper(new ComponentResultStringifier(this));
placeholderService().addGlobalContext(new GlobalTextHandlingContext(this)); placeholderService().addGlobalContext(new GlobalTextHandlingContext(this));
// Register PlayerProvider listeners // Modules
playerProvider().subscribe();
registerModule(ChannelUpdaterModule::new); registerModule(ChannelUpdaterModule::new);
registerModule(GameCommandModule::new);
registerModule(GlobalChannelLookupModule::new); registerModule(GlobalChannelLookupModule::new);
registerModule(DiscordAPIEventModule::new); registerModule(DiscordAPIEventModule::new);
registerModule(GroupSyncModule::new); registerModule(GroupSyncModule::new);
@ -401,6 +353,14 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
registerModule(DiscordMessageMirroringModule::new); registerModule(DiscordMessageMirroringModule::new);
registerModule(JoinMessageModule::new); registerModule(JoinMessageModule::new);
registerModule(LeaveMessageModule::new); registerModule(LeaveMessageModule::new);
registerModule(DiscordInviteModule::new);
// Initial load
try {
invokeReload(ReloadFlag.ALL, true).get();
} catch (ExecutionException e) {
throw e.getCause();
}
} }
private StorageType getStorageType() { private StorageType getStorageType() {
@ -425,7 +385,102 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
} }
@OverridingMethodsMustInvokeSuper @OverridingMethodsMustInvokeSuper
protected void reload() { protected void reload(Set<ReloadFlag> flags, boolean initial) throws Throwable {
if (!initial) {
logger().info("Reloading DiscordSRV...");
}
if (flags.contains(ReloadFlag.CONFIG)) {
try {
connectionConfigManager().load();
configManager().load();
} catch (Throwable t) {
setStatus(Status.FAILED_TO_LOAD_CONFIG);
throw t;
}
channelConfig().reload();
}
if (flags.contains(ReloadFlag.LINKED_ACCOUNT_PROVIDER)) {
LinkedAccountConfig linkedAccountConfig = config().linkedAccounts;
if (linkedAccountConfig != null && linkedAccountConfig.enabled) {
String provider = linkedAccountConfig.provider;
switch (provider) {
case "auto":
case "storage":
linkProvider = new StorageLinker(this);
logger().info("Using storage for linked accounts");
break;
case "memory": {
linkProvider = new MemoryLinker();
logger().warning("Using memory for linked accounts");
logger().warning("Linked accounts will be lost upon restart");
break;
}
default: {
linkProvider = null;
logger().error("Unknown linked account provider: \"" + provider + "\", linked accounts will not be used");
break;
}
}
} else {
linkProvider = null;
logger().info("Linked accounts are disabled");
}
}
if (flags.contains(ReloadFlag.STORAGE)) {
if (storage != null) {
storage.close();
}
try {
try {
StorageType storageType = getStorageType();
logger().info("Using " + storageType.prettyName() + " as storage");
if (storageType.hikari() && !hikariLoaded) {
hikariLoaded = true;
DependencyLoader.hikari(this).process(classpathAppender()).get();
}
storage = storageType.storageFunction().apply(this);
storage.initialize();
logger().info("Storage connection successfully established");
} catch (ExecutionException e) {
throw new StorageException(e.getCause());
} catch (StorageException e) {
throw e;
} catch (Throwable t) {
throw new StorageException(t);
}
} catch (StorageException e) {
e.log(this);
logger().error("Failed to connect to storage");
setStatus(Status.FAILED_TO_START);
return;
}
}
if (flags.contains(ReloadFlag.DISCORD_CONNECTION)) {
try {
if (discordConnectionManager.instance() != null) {
discordConnectionManager.reconnect().get();
} else {
discordConnectionManager.connect().get();
}
waitForStatus(Status.CONNECTED, 20, TimeUnit.SECONDS);
} catch (ExecutionException e) {
throw e.getCause();
}
}
if (flags.contains(ReloadFlag.MODULES)) {
moduleManager.reload();
}
if (!initial) {
eventBus().publish(new DiscordSRVReloadedEvent(flags));
logger().info("Reload complete.");
}
} }
} }

View File

@ -21,6 +21,7 @@ package com.discordsrv.common;
import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.api.module.type.Module; import com.discordsrv.api.module.type.Module;
import com.discordsrv.common.channel.ChannelConfigHelper; import com.discordsrv.common.channel.ChannelConfigHelper;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.component.ComponentFactory; import com.discordsrv.common.component.ComponentFactory;
import com.discordsrv.common.config.connection.ConnectionConfig; import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig; import com.discordsrv.common.config.main.MainConfig;
@ -47,7 +48,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Locale; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -62,6 +63,7 @@ public interface DiscordSRV extends DiscordSRVApi {
PluginManager pluginManager(); PluginManager pluginManager();
OnlineMode onlineMode(); OnlineMode onlineMode();
ClasspathAppender classpathAppender(); ClasspathAppender classpathAppender();
ICommandHandler commandHandler();
@NotNull AbstractPlayerProvider<?, ?> playerProvider(); @NotNull AbstractPlayerProvider<?, ?> playerProvider();
// DiscordSRVApi // DiscordSRVApi
@ -126,6 +128,6 @@ public interface DiscordSRV extends DiscordSRVApi {
// Lifecycle // Lifecycle
CompletableFuture<Void> invokeEnable(); CompletableFuture<Void> invokeEnable();
CompletableFuture<Void> invokeDisable(); CompletableFuture<Void> invokeDisable();
CompletableFuture<Void> invokeReload(); CompletableFuture<Void> invokeReload(Set<ReloadFlag> flags, boolean silent);
} }

View File

@ -22,9 +22,7 @@ import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel; import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel; import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel; import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel;
import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.channel.GameChannelLookupEvent; import com.discordsrv.api.event.events.channel.GameChannelLookupEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig; import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.base.ChannelConfig; import com.discordsrv.common.config.main.channels.base.ChannelConfig;
@ -94,16 +92,9 @@ public class ChannelConfigHelper {
} }
return map; return map;
}); });
discordSRV.eventBus().subscribe(this);
} }
@Subscribe public void reload() {
public void onReload(DiscordSRVReloadEvent event) {
if (!event.isConfig()) {
return;
}
Map<Long, Map<String, BaseChannelConfig>> newMap = new HashMap<>(); Map<Long, Map<String, BaseChannelConfig>> newMap = new HashMap<>();
for (Map.Entry<String, BaseChannelConfig> entry : channels().entrySet()) { for (Map.Entry<String, BaseChannelConfig> entry : channels().entrySet()) {
String channelName = entry.getKey(); String channelName = entry.getKey();

View File

@ -115,5 +115,4 @@ public class ChannelUpdaterModule extends AbstractModule<DiscordSRV> {
} }
} }
} }

View File

@ -0,0 +1,77 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.command.DiscordSRVCommand;
import com.discordsrv.common.command.game.command.subcommand.LinkCommand;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.main.CommandConfig;
import com.discordsrv.common.module.type.AbstractModule;
import java.util.HashSet;
import java.util.Set;
public class GameCommandModule extends AbstractModule<DiscordSRV> {
private final Set<GameCommand> commands = new HashSet<>();
private final GameCommand primaryCommand;
private final GameCommand discordAlias;
private final GameCommand linkCommand;
public GameCommandModule(DiscordSRV discordSRV) {
super(discordSRV);
this.primaryCommand = DiscordSRVCommand.get(discordSRV);
this.discordAlias = GameCommand.literal("discord").redirect(primaryCommand);
this.linkCommand = LinkCommand.get(discordSRV);
registerCommand(primaryCommand);
}
@Override
public void reload() {
CommandConfig config = discordSRV.config().command;
if (config == null) {
return;
}
registerCommand(primaryCommand);
if (config.useDiscordCommand) {
registerCommand(discordAlias);
}
if (config.useLinkAlias) {
registerCommand(linkCommand);
}
}
private void registerCommand(GameCommand command) {
ICommandHandler handler = discordSRV.commandHandler();
if (handler == null) {
return;
}
if (!commands.add(command)) {
return;
}
handler.registerCommand(command);
}
}

View File

@ -0,0 +1,473 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.abstraction;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.common.function.CheckedFunction;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class GameCommand {
private static final GameCommandSuggester BOOLEANS_SUGGESTER = (player, previous, current) -> {
if (current.isEmpty()) {
return Arrays.asList("true", "false");
} else if ("true".startsWith(current)) {
return Collections.singletonList("true");
} else if ("false".startsWith(current)) {
return Collections.singletonList("false");
} else {
return Collections.emptyList();
}
};
public static GameCommand literal(String label) {
return new GameCommand(label, ArgumentType.LITERAL);
}
public static GameCommand booleanArgument(String label) {
return new GameCommand(label, ArgumentType.BOOLEAN).suggester(BOOLEANS_SUGGESTER);
}
public static GameCommand doubleArgument(String label) {
return new GameCommand(label, ArgumentType.DOUBLE);
}
public static GameCommand floatArgument(String label) {
return new GameCommand(label, ArgumentType.FLOAT);
}
public static GameCommand integerArgument(String label) {
return new GameCommand(label, ArgumentType.INTEGER);
}
public static GameCommand longArgument(String label) {
return new GameCommand(label, ArgumentType.LONG);
}
public static GameCommand string(String label) {
return new GameCommand(label, ArgumentType.STRING);
}
public static GameCommand stringWord(String label) {
return new GameCommand(label, ArgumentType.STRING_WORD);
}
public static GameCommand stringGreedy(String label) {
return new GameCommand(label, ArgumentType.STRING_GREEDY);
}
private final ExecutorProxy executorProxy = new ExecutorProxy();
private final SuggesterProxy suggesterProxy = new SuggesterProxy();
// Command info
private GameCommand parent = null;
private final String label;
private final ArgumentType argumentType;
private final List<GameCommand> children;
private GameCommand redirection = null;
// Permission
private String requiredPermission;
private Component noPermissionMessage = null;
// Executor & suggestor
private GameCommandExecutor commandExecutor = null;
private GameCommandSuggester commandSuggester = null;
// Argument type bounds
private double maxValue = Double.MAX_VALUE;
private double minValue = Double.MIN_VALUE;
private GameCommand(String label, ArgumentType argumentType) {
this.label = label;
this.argumentType = argumentType;
this.children = new ArrayList<>();
}
private GameCommand(GameCommand original) {
this.parent = original.parent;
this.label = original.label;
this.argumentType = original.argumentType;
this.children = original.children;
this.redirection = original.redirection;
this.requiredPermission = original.requiredPermission;
this.noPermissionMessage = original.noPermissionMessage;
this.commandExecutor = original.commandExecutor;
this.commandSuggester = original.commandSuggester;
this.maxValue = original.maxValue;
this.minValue = original.minValue;
}
public String getLabel() {
return label;
}
public ArgumentType getArgumentType() {
return argumentType;
}
/**
* Adds a sub command. You can only have multiple literals (with unique labels) or a single non-literal one.
*/
public GameCommand then(GameCommand child) {
if (redirection != null) {
throw new IllegalStateException("Cannot add children to a redirected node");
}
for (GameCommand builder : children) {
if (builder.getArgumentType() == ArgumentType.LITERAL
&& child.getArgumentType() == ArgumentType.LITERAL
&& builder.getLabel().equals(child.getLabel())) {
throw new IllegalArgumentException("Duplicate literal with label \"" + child.label + "\"");
}
if (child.getArgumentType() == ArgumentType.LITERAL && builder.getArgumentType() != ArgumentType.LITERAL) {
throw new IllegalStateException("A non-literal is already present, cannot add literal");
}
if (child.getArgumentType() != ArgumentType.LITERAL) {
throw new IllegalStateException("Cannot add non-literal when another child is already present");
}
}
if (child.getNoPermissionMessage() == null && noPermissionMessage != null) {
child.noPermissionMessage(noPermissionMessage);
}
child.parent = this;
this.children.add(child);
return this;
}
public List<GameCommand> getChildren() {
return children;
}
public GameCommand redirect(GameCommand redirection) {
if (!children.isEmpty()) {
throw new IllegalStateException("Cannot redirect a node with children");
}
if (requiredPermission != null) {
throw new IllegalStateException("Cannot redirect a node with a required permission");
}
this.redirection = redirection;
return this;
}
public GameCommand getRedirection() {
return redirection;
}
public GameCommand requiredPermission(String permission) {
if (redirection != null) {
throw new IllegalStateException("Cannot required permissions on a node with a redirection");
}
this.requiredPermission = permission;
return this;
}
public String getRequiredPermission() {
if (redirection != null) {
return redirection.getRequiredPermission();
}
return requiredPermission;
}
public GameCommand noPermissionMessage(Component noPermissionMessage) {
this.noPermissionMessage = noPermissionMessage;
return this;
}
public Component getNoPermissionMessage() {
return noPermissionMessage;
}
public GameCommand executor(GameCommandExecutor executor) {
this.commandExecutor = executor;
return this;
}
public GameCommandExecutor getExecutor() {
return executorProxy;
}
/**
* Cannot be used with {@link #literal(String)}.
*/
public GameCommand suggester(GameCommandSuggester suggester) {
if (argumentType == ArgumentType.LITERAL) {
throw new IllegalArgumentException("Cannot use on argument type literal");
}
this.commandSuggester = suggester;
return this;
}
public GameCommandSuggester getSuggester() {
return suggesterProxy;
}
/**
* Can only be used on number argument types.
*/
public GameCommand minValue(double minValue) {
mustBeNumber();
this.minValue = minValue;
return this;
}
public double getMinValue() {
return minValue;
}
/**
* Can only be used on number argument types.
*/
public GameCommand maxValue(double maxValue) {
mustBeNumber();
this.maxValue = maxValue;
return this;
}
public double getMaxValue() {
return maxValue;
}
private void mustBeNumber() {
if (!argumentType.number()) {
throw new IllegalArgumentException("Cannot be used on this argument type");
}
}
public boolean hasPermission(ICommandSender sender) {
String requiredPermission = getRequiredPermission();
return requiredPermission == null || sender.hasPermission(requiredPermission);
}
public void sendNoPermission(ICommandSender sender) {
sender.sendMessage(noPermissionMessage != null
? noPermissionMessage
: Component.text("No permission", NamedTextColor.RED));
}
public ArgumentResult checkArgument(String argument) {
switch (getArgumentType()) {
case LITERAL: return ArgumentResult.fromBoolean(argument.equals(getLabel()));
case STRING: {
if (argument.startsWith("\"")) {
boolean single = argument.length() == 1;
if (argument.endsWith("\"") && !single) {
return new ArgumentResult(argument.substring(1, argument.length() - 1), MatchResult.MATCHES);
}
return new ArgumentResult(single ? "" : argument.substring(1), single ? MatchResult.END : MatchResult.CONTINUE);
}
if (argument.endsWith("\"")) {
return new ArgumentResult(argument.substring(0, argument.length() - 1), MatchResult.END);
}
return new ArgumentResult(argument, MatchResult.MATCHES);
}
case STRING_GREEDY: return new ArgumentResult(argument, GameCommand.MatchResult.CONTINUE);
default: {
try {
return new ArgumentResult(getArgumentType().function().apply(argument), MatchResult.MATCHES);
} catch (Throwable ignored) {
return ArgumentResult.NO_MATCH;
}
}
}
}
public String getArgumentLabel() {
if (argumentType == ArgumentType.LITERAL) {
return label;
} else {
return "<" + label + ">";
}
}
public Component describe() {
StringBuilder stringBuilder = new StringBuilder();
GameCommand current = this;
while (current != null) {
stringBuilder.insert(0, current.getArgumentLabel() + " ");
current = current.parent;
}
String command = "/" + stringBuilder.substring(1);
return Component.text(command, NamedTextColor.AQUA);
}
public void sendCommandInstructions(ICommandSender sender) {
if (children.isEmpty()) {
throw new IllegalStateException("No children");
}
TextComponent.Builder builder = Component.text();
builder.append(describe().color(NamedTextColor.GRAY).append(Component.text(" available subcommands:")));
boolean anyAvailable = false;
for (GameCommand child : children) {
if (!child.hasPermission(sender)) {
continue;
}
anyAvailable = true;
builder.append(Component.newline()).append(child.describe());
}
if (!anyAvailable) {
builder.append(Component.newline())
.append(Component.text("No available subcommands", NamedTextColor.RED));
}
sender.sendMessage(builder.build());
}
@SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException", "MethodDoesntCallSuperMethod"})
@Override
protected GameCommand clone() {
return new GameCommand(this);
}
public static class ArgumentResult {
public static final ArgumentResult EMPTY_MATCH = new ArgumentResult(MatchResult.MATCHES);
public static final ArgumentResult NO_MATCH = new ArgumentResult(MatchResult.NO_MATCH);
public static ArgumentResult fromBoolean(boolean value) {
return value ? EMPTY_MATCH : NO_MATCH;
}
private final Object value;
private final MatchResult result;
private ArgumentResult(MatchResult result) {
this(null, result);
}
public ArgumentResult(Object value, MatchResult result) {
this.value = value;
this.result = result;
}
public Object value() {
return value;
}
public MatchResult result() {
return result;
}
}
public enum MatchResult {
MATCHES,
CONTINUE,
END,
NO_MATCH
}
public enum ArgumentType {
LITERAL(),
BOOLEAN(false, input -> {
boolean value;
if ((value = input.equals("true")) || input.equals("false")) {
return value;
}
throw new IllegalArgumentException();
}),
DOUBLE(true, Double::parseDouble),
FLOAT(true, Float::parseFloat),
INTEGER(true, Integer::parseInt),
LONG(true, Long::parseLong),
STRING_WORD(false, input -> input),
STRING(false, true),
STRING_GREEDY(false, true);
private final boolean number;
private final boolean multi;
private final CheckedFunction<String, Object> function;
ArgumentType() {
this(false, null);
}
ArgumentType(boolean number, CheckedFunction<String, Object> function) {
this(number, false, function);
}
ArgumentType(boolean number, boolean multi) {
this(number, multi, null);
}
ArgumentType(boolean number, boolean multi, CheckedFunction<String, Object> function) {
this.number = number;
this.multi = multi;
this.function = function;
}
public boolean number() {
return number;
}
public boolean multi() {
return multi;
}
public CheckedFunction<String, Object> function() {
return function;
}
}
private class ExecutorProxy implements GameCommandExecutor {
@Override
public void execute(ICommandSender sender, GameCommandArguments arguments) {
if (!hasPermission(sender)) {
sendNoPermission(sender);
return;
}
if (commandExecutor != null) {
commandExecutor.execute(sender, arguments);
} else if (!children.isEmpty()) {
sendCommandInstructions(sender);
} else {
throw new IllegalStateException("Command (" + GameCommand.this + ") doesn't have children and has no executor");
}
}
}
private class SuggesterProxy implements GameCommandSuggester {
@Override
public List<String> suggestValues(
ICommandSender sender,
GameCommandArguments previousArguments,
String currentInput
) {
if (!hasPermission(sender)) {
return Collections.emptyList();
}
if (commandSuggester != null) {
return commandSuggester.suggestValues(sender, previousArguments, currentInput);
} else {
return Collections.emptyList();
}
}
}
}

View File

@ -0,0 +1,29 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.abstraction;
@FunctionalInterface
public interface GameCommandArguments {
<T> T get(String label, Class<T> type);
default Integer getInt(String label) {
return get(label, Integer.class);
}
}

View File

@ -0,0 +1,27 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.abstraction;
import com.discordsrv.common.command.game.sender.ICommandSender;
@FunctionalInterface
public interface GameCommandExecutor {
void execute(ICommandSender sender, GameCommandArguments arguments);
}

View File

@ -0,0 +1,30 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.abstraction;
import com.discordsrv.common.command.game.sender.ICommandSender;
import java.util.List;
@FunctionalInterface
public interface GameCommandSuggester {
List<String> suggestValues(ICommandSender sender, GameCommandArguments previousArguments, String currentInput);
}

View File

@ -0,0 +1,64 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.command;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.command.subcommand.LinkCommand;
import com.discordsrv.common.command.game.command.subcommand.ReloadCommand;
import com.discordsrv.common.command.game.command.subcommand.VersionCommand;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.common.component.util.ComponentUtil;
public class DiscordSRVCommand implements GameCommandExecutor {
private static GameCommand INSTANCE;
public static GameCommand get(DiscordSRV discordSRV) {
if (INSTANCE == null) {
INSTANCE = GameCommand.literal("discordsrv")
.requiredPermission("discordsrv.player.command")
.executor(new DiscordSRVCommand(discordSRV))
.then(LinkCommand.get(discordSRV))
.then(ReloadCommand.get(discordSRV))
.then(VersionCommand.get(discordSRV));
}
return INSTANCE;
}
private final DiscordSRV discordSRV;
public DiscordSRVCommand(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void execute(ICommandSender sender, GameCommandArguments arguments) {
MinecraftComponent component = discordSRV.componentFactory()
.enhancedBuilder(discordSRV.config().command.discordFormat)
.addContext(sender)
.applyPlaceholderService()
.build();
sender.sendMessage(ComponentUtil.fromAPI(component));
}
}

View File

@ -0,0 +1,51 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.command.subcommand;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.sender.ICommandSender;
public class LinkCommand implements GameCommandExecutor {
private static GameCommand INSTANCE;
public static GameCommand get(DiscordSRV discordSRV) {
if (INSTANCE == null) {
INSTANCE = GameCommand.literal("link")
.requiredPermission("discordsrv.player.link")
.executor(new LinkCommand(discordSRV));
}
return INSTANCE;
}
private final DiscordSRV discordSRV;
public LinkCommand(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void execute(ICommandSender sender, GameCommandArguments arguments) {
}
}

View File

@ -0,0 +1,148 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.command.subcommand;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.abstraction.GameCommandSuggester;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
public class ReloadCommand implements GameCommandExecutor, GameCommandSuggester {
private static GameCommand INSTANCE;
public static GameCommand get(DiscordSRV discordSRV) {
if (INSTANCE == null) {
ReloadCommand cmd = new ReloadCommand(discordSRV);
INSTANCE = GameCommand.literal("reload")
.requiredPermission("discordsrv.admin.reload")
.executor(cmd)
.then(
GameCommand.stringGreedy("flags")
.executor(cmd).suggester(cmd)
);
}
return INSTANCE;
}
private final DiscordSRV discordSRV;
public ReloadCommand(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void execute(ICommandSender sender, GameCommandArguments arguments) {
AtomicBoolean dangerousFlags = new AtomicBoolean(false);
Set<DiscordSRV.ReloadFlag> flags = getFlagsFromArguments(sender, arguments, dangerousFlags);
if (flags == null) {
flags = DiscordSRV.ReloadFlag.DEFAULT_FLAGS;
}
if (dangerousFlags.get()) {
sender.sendMessage(Component.text("You can add -confirm to the end of the command if you wish to proceed anyway", NamedTextColor.DARK_RED));
return;
}
if (flags.isEmpty()) {
sender.sendMessage(Component.text("Please specify at least one valid flag", NamedTextColor.RED));
return;
}
discordSRV.invokeReload(flags, false).whenComplete((v, t) -> {
if (t != null) {
discordSRV.logger().error("Failed to reload", t);
sender.sendMessage(
Component.text()
.append(Component.text("Reload failed.", NamedTextColor.DARK_RED, TextDecoration.BOLD))
.append(Component.text("Please check the server console/log for more details."))
);
} else {
sender.sendMessage(Component.text("Reload successful", NamedTextColor.GRAY));
}
});
}
private Set<DiscordSRV.ReloadFlag> getFlagsFromArguments(ICommandSender sender, GameCommandArguments arguments, AtomicBoolean dangerousFlags) {
String argument = null;
try {
argument = arguments.get("flags", String.class);
} catch (IllegalArgumentException ignored) {}
if (argument == null) {
return null;
}
List<String> parts = new ArrayList<>(Arrays.asList(argument.split(" ")));
boolean confirm = parts.remove("-confirm");
Set<DiscordSRV.ReloadFlag> flags = new LinkedHashSet<>();
for (String part : parts) {
try {
DiscordSRV.ReloadFlag flag = DiscordSRV.ReloadFlag.valueOf(part.toUpperCase(Locale.ROOT));
if (flag.requiresConfirm() && !confirm) {
dangerousFlags.set(true);
sender.sendMessage(
Component.text("Reloading ", NamedTextColor.RED)
.append(Component.text(part, NamedTextColor.GRAY))
.append(Component.text(" might cause DiscordSRV to end up in a unrecoverable state", NamedTextColor.RED))
);
continue;
}
flags.add(flag);
} catch (IllegalArgumentException ignored) {
sender.sendMessage(Component.text("Flag ", NamedTextColor.RED)
.append(Component.text(part, NamedTextColor.GRAY))
.append(Component.text(" is not known", NamedTextColor.RED)));
}
}
return flags;
}
@Override
public List<String> suggestValues(
ICommandSender sender,
GameCommandArguments previousArguments,
String currentInput
) {
int lastSpace = currentInput.lastIndexOf(' ') + 1;
String last = currentInput.substring(lastSpace);
String beforeLastSpace = currentInput.substring(0, lastSpace);
List<String> options = DiscordSRV.ReloadFlag.ALL.stream()
.map(flag -> flag.name().toLowerCase(Locale.ROOT))
.filter(flag -> flag.startsWith(last))
.collect(Collectors.toList());
for (String part : currentInput.split(" ")) {
options.remove(part);
}
return options.stream().map(flag -> beforeLastSpace + flag).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,58 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.command.subcommand;
import com.discordsrv.api.color.Color;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
public class VersionCommand implements GameCommandExecutor {
private static GameCommand INSTANCE;
public static GameCommand get(DiscordSRV discordSRV) {
if (INSTANCE == null) {
INSTANCE = GameCommand.literal("version")
.requiredPermission("discordsrv.admin.version")
.executor(new VersionCommand(discordSRV));
}
return INSTANCE;
}
private final DiscordSRV discordSRV;
public VersionCommand(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override
public void execute(ICommandSender sender, GameCommandArguments arguments) {
sender.sendMessage(
Component.text().content("Running DiscordSRV ").color(TextColor.color(Color.BLURPLE.rgb()))
.append(Component.text("v" + discordSRV.version(), NamedTextColor.GRAY))
);
}
}

View File

@ -0,0 +1,237 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.handler;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
public class BasicCommandHandler implements ICommandHandler {
private final Map<String, GameCommand> commands = new HashMap<>();
@Override
public void registerCommand(GameCommand command) {
commands.put(command.getLabel(), command);
}
private GameCommand processCommand(
GameCommand command,
List<String> arguments,
Arguments argumentValues,
BiConsumer<GameCommand, Arguments> tooManyArguments,
BiConsumer<GameCommand, Arguments> unclosedQuote,
List<String> literalOptions
) {
GameCommand redirection = command.getRedirection();
if (redirection != null) {
command = redirection;
}
if (!arguments.isEmpty()) {
List<String> currentOptions = new ArrayList<>();
for (GameCommand child : command.getChildren()) {
if (child.getArgumentType() == GameCommand.ArgumentType.LITERAL) {
currentOptions.add(child.getLabel());
}
GameCommand.ArgumentResult result = child.checkArgument(arguments.get(0));
GameCommand.MatchResult matchResult = result.result();
if (matchResult == GameCommand.MatchResult.NO_MATCH) {
continue;
}
arguments.remove(0);
Object value;
if (child.getArgumentType().multi()) {
StringBuilder content = new StringBuilder((String) result.value());
if (matchResult == GameCommand.MatchResult.END) {
// Premature end
if (unclosedQuote != null) {
unclosedQuote.accept(command, argumentValues);
}
return unclosedQuote == null ? command : null;
}
boolean stringStart = matchResult == GameCommand.MatchResult.CONTINUE;
while (stringStart && !arguments.isEmpty()) {
result = child.checkArgument(arguments.remove(0));
Object resultValue = result.value();
content.append(' ').append(resultValue != null ? resultValue : "");
matchResult = result.result();
if (matchResult == GameCommand.MatchResult.END) {
stringStart = false;
}
}
if (stringStart && child.getArgumentType() == GameCommand.ArgumentType.STRING) {
// Unclosed quote
if (unclosedQuote != null) {
unclosedQuote.accept(command, argumentValues);
} else {
argumentValues.put(child.getLabel(), "\"" + content);
}
return unclosedQuote == null ? child : null;
}
value = content.toString();
} else {
value = result.value();
}
argumentValues.put(child.getLabel(), value);
return processCommand(child, arguments, argumentValues, tooManyArguments, unclosedQuote, literalOptions);
}
if (literalOptions != null) {
literalOptions.addAll(currentOptions);
return command;
}
}
if (!arguments.isEmpty() && tooManyArguments != null) {
tooManyArguments.accept(command, argumentValues);
return null;
}
return command;
}
private <T> T useCommand(
String command,
List<String> arguments,
BiConsumer<GameCommand, Arguments> tooManyArguments,
BiConsumer<GameCommand, Arguments> unclosedQuote,
BiFunction<GameCommand, Arguments, T> function,
List<String> literalOptions
) {
command = command.substring(command.lastIndexOf(':') + 1);
GameCommand commandBuilder = commands.get(command);
if (commandBuilder == null) {
return null;
}
Arguments args = new Arguments();
GameCommand subCommand = processCommand(commandBuilder, new ArrayList<>(arguments), args, tooManyArguments, unclosedQuote, literalOptions);
if (subCommand == null) {
return null;
}
return function.apply(subCommand, args);
}
public void execute(ICommandSender sender, String command, List<String> arguments) {
useCommand(
command,
arguments,
(cmd, args) -> {
if (!cmd.hasPermission(sender)) {
cmd.sendNoPermission(sender);
return;
}
error(sender, command, arguments, args, "Incorrect argument for command");
},
(cmd, args) -> {
if (!cmd.hasPermission(sender)) {
cmd.sendNoPermission(sender);
return;
}
error(sender, command, arguments, args, "Unclosed quoted string");
},
(cmd, args) -> {
cmd.getExecutor().execute(sender, args);
return null;
},
null);
}
private void error(ICommandSender sender, String command, List<String> arguments, Arguments args, String title) {
// Mimic brigadier behaviour (en-US)
StringBuilder builder = new StringBuilder(command);
for (Object value : args.getValues().values()) {
builder.append(" ").append(value);
}
int length = builder.length();
String pre = length >= 10 ? "..." + builder.substring(length - 9, length) : builder.toString();
sender.sendMessage(
Component.text()
.append(Component.text(title, NamedTextColor.RED))
.append(Component.newline())
.append(Component.text(pre + " ", NamedTextColor.GRAY))
.append(Component.text(String.join(" ", arguments), NamedTextColor.RED, TextDecoration.UNDERLINED))
.append(Component.text("<--[HERE]", NamedTextColor.RED, TextDecoration.ITALIC))
);
}
public List<String> suggest(ICommandSender sender, String command, List<String> arguments) {
List<String> suggestions = new ArrayList<>();
return useCommand(
command,
arguments,
null,
null,
(cmd, args) -> {
suggestions.addAll(
cmd.getSuggester().suggestValues(sender, args, String.valueOf(args.get(cmd.getLabel(), Object.class)))
.stream()
.map(value -> value.substring(value.lastIndexOf(' ') + 1))
.collect(Collectors.toList())
);
return suggestions;
},
suggestions
);
}
private static class Arguments implements GameCommandArguments {
private final Map<String, Object> values = new LinkedHashMap<>();
@SuppressWarnings("unchecked")
@Override
public <T> T get(String label, Class<T> type) {
return (T) values.get(label);
}
public void put(String label, Object value) {
values.put(label, value);
}
public Map<String, Object> getValues() {
return values;
}
@Override
public String toString() {
return "Arguments{" +
"values=" + values +
'}';
}
}
}

View File

@ -0,0 +1,26 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.handler;
import com.discordsrv.common.command.game.abstraction.GameCommand;
public interface ICommandHandler {
void registerCommand(GameCommand command);
}

View File

@ -0,0 +1,136 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.command.game.handler.util;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.abstraction.GameCommandSuggester;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.*;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
* Helper class to convert DiscordSRV's abstract command tree into a brigadier one.
*/
public final class BrigadierUtil {
private static final Map<GameCommand, CommandNode<?>> CACHE = new ConcurrentHashMap<>();
private BrigadierUtil() {}
public static <S> LiteralCommandNode<S> convertToBrigadier(GameCommand command, Function<S, ICommandSender> commandSenderMapper) {
return (LiteralCommandNode<S>) convert(command, commandSenderMapper);
}
@SuppressWarnings("unchecked")
private static <S> CommandNode<S> convert(GameCommand commandBuilder, Function<S, ICommandSender> commandSenderMapper) {
CommandNode<S> alreadyConverted = (CommandNode<S>) CACHE.get(commandBuilder);
if (alreadyConverted != null) {
return alreadyConverted;
}
GameCommand.ArgumentType type = commandBuilder.getArgumentType();
String label = commandBuilder.getLabel();
GameCommandExecutor executor = commandBuilder.getExecutor();
GameCommandSuggester suggester = commandBuilder.getSuggester();
GameCommand redirection = commandBuilder.getRedirection();
String requiredPermission = commandBuilder.getRequiredPermission();
ArgumentBuilder<S, ?> argumentBuilder;
if (type == GameCommand.ArgumentType.LITERAL) {
argumentBuilder = LiteralArgumentBuilder.literal(label);
} else {
argumentBuilder = RequiredArgumentBuilder.argument(label, convertType(commandBuilder));
}
for (GameCommand child : commandBuilder.getChildren()) {
argumentBuilder.then(convert(child, commandSenderMapper));
}
if (redirection != null) {
CommandNode<S> redirectNode = (CommandNode<S>) CACHE.get(redirection);
if (redirectNode == null) {
redirectNode = convert(redirection, commandSenderMapper);
}
argumentBuilder.redirect(redirectNode);
}
if (requiredPermission != null) {
argumentBuilder.requires(sender -> {
ICommandSender commandSender = commandSenderMapper.apply(sender);
return commandSender.hasPermission(requiredPermission);
});
}
if (executor != null) {
argumentBuilder.executes(context -> {
executor.execute(
commandSenderMapper.apply(context.getSource()),
context::getArgument
);
return Command.SINGLE_SUCCESS;
});
}
if (suggester != null && argumentBuilder instanceof RequiredArgumentBuilder) {
((RequiredArgumentBuilder<S, ?>) argumentBuilder).suggests((context, builder) -> {
try {
List<?> suggestions = suggester.suggestValues(
commandSenderMapper.apply(context.getSource()),
context::getArgument,
builder.getRemaining()
);
suggestions.forEach(suggestion -> builder.suggest(suggestion.toString()));
} catch (Throwable t) {
t.printStackTrace();
}
return CompletableFuture.completedFuture(builder.build());
});
}
CommandNode<S> node = argumentBuilder.build();
CACHE.put(commandBuilder, node);
return node;
}
private static ArgumentType<?> convertType(GameCommand builder) {
GameCommand.ArgumentType argumentType = builder.getArgumentType();
double min = builder.getMinValue();
double max = builder.getMaxValue();
switch (argumentType) {
case LONG: return LongArgumentType.longArg((long) min, (long) max);
case FLOAT: return FloatArgumentType.floatArg((float) min, (float) max);
case DOUBLE: return DoubleArgumentType.doubleArg(min, max);
case STRING: return StringArgumentType.string();
case STRING_WORD: return StringArgumentType.word();
case STRING_GREEDY: return StringArgumentType.greedyString();
case BOOLEAN: return BoolArgumentType.bool();
case INTEGER: return IntegerArgumentType.integer((int) min, (int) max);
}
throw new IllegalStateException();
}
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.config.main;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ConfigSerializable
public class CommandConfig {
@Comment("If the /discord command should be set by DiscordSRV")
public boolean useDiscordCommand = true;
@Comment("Use /link as a alias for /discord link")
public boolean useLinkAlias = false;
@Comment("Discord command format, player placeholders may be used")
public String discordFormat = "[click:open_url:%discord_invite%]&b&lClick here &r&ato join our Discord server!";
}

View File

@ -0,0 +1,38 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.config.main;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
@ConfigSerializable
public class DiscordInviteConfig {
@Comment("Manually enter a invite url here, if this isn't set this is ignored and the options below will take effect")
public String inviteUrl = "";
@Comment("If the bot is only in one Discord server, it will attempt to get it's vanity url")
public boolean attemptToUseVanityUrl = true;
@Comment("If the bot is only in one Discord server, it will attempt to automatically create a invite for it.\n"
+ "The bot will only attempt to do so if it has permission to \"Create Invite\"\n"
+ "The server must also have a rules channel (available for community servers) or default channel (automatically determined by Discord)")
public boolean autoCreateInvite = true;
}

View File

@ -49,4 +49,10 @@ public class MainConfig implements Config {
@Comment("Configuration options for group-role synchronization") @Comment("Configuration options for group-role synchronization")
public GroupSyncConfig groupSync = new GroupSyncConfig(); public GroupSyncConfig groupSync = new GroupSyncConfig();
@Comment("Command configuration")
public CommandConfig command = new CommandConfig();
@Comment("The %discord_invite% placeholder configuration. The below options will be attempted in the order they are in")
public DiscordInviteConfig invite = new DiscordInviteConfig();
} }

View File

@ -33,5 +33,5 @@ public class MirroringConfig {
@Comment("The format of the username of mirrored messages\n" @Comment("The format of the username of mirrored messages\n"
+ "It's recommended to include some special character if in-game messages use webhooks,\n" + "It's recommended to include some special character if in-game messages use webhooks,\n"
+ "in order to prevent Discord users and in-game players being grouped together") + "in order to prevent Discord users and in-game players being grouped together")
public String usernameFormat = "%user_effective_name% [M]"; public String usernameFormat = "%user_effective_name% \uD83D\uDD03";
} }

View File

@ -18,6 +18,7 @@
package com.discordsrv.common.dependency; package com.discordsrv.common.dependency;
import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.logging.Logger; import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.scheduler.threadfactory.CountingForkJoinWorkerThreadFactory; import com.discordsrv.common.scheduler.threadfactory.CountingForkJoinWorkerThreadFactory;
@ -91,7 +92,7 @@ public class InitialDependencyLoader {
if (discordSRV == null) { if (discordSRV == null) {
return; return;
} }
discordSRV.invokeReload(); discordSRV.invokeReload(DiscordSRVApi.ReloadFlag.DEFAULT_FLAGS, false);
} }
public void disable(DiscordSRV discordSRV) { public void disable(DiscordSRV discordSRV) {

View File

@ -69,7 +69,11 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
@Override @Override
public void reload() { public void reload() {
synchronized (pairs) { synchronized (pairs) {
pairs.values().forEach(future -> future.cancel(false)); pairs.values().forEach(future -> {
if (future != null) {
future.cancel(false);
}
});
pairs.clear(); pairs.clear();
groupsToPairs.clear(); groupsToPairs.clear();
rolesToPairs.clear(); rolesToPairs.clear();

View File

@ -0,0 +1,98 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.invite;
import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.DiscordInviteConfig;
import com.discordsrv.common.module.type.AbstractModule;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.TextChannel;
import java.util.List;
public class DiscordInviteModule extends AbstractModule<DiscordSRV> {
private static final String UNKNOWN_INVITE = "https://discord.gg/#Could_not_get_invite,_please_check_your_configuration";
private String invite;
public DiscordInviteModule(DiscordSRV discordSRV) {
super(discordSRV);
discordSRV.placeholderService().addGlobalContext(this);
}
@Override
public void reload() {
JDA jda = discordSRV.jda().orElse(null);
if (jda == null) {
return;
}
DiscordInviteConfig config = discordSRV.config().invite;
// Manual
String invite = config.inviteUrl;
if (invite != null && !invite.isEmpty()) {
this.invite = invite;
return;
}
List<Guild> guilds = jda.getGuilds();
if (guilds.size() != 1) {
return;
}
Guild guild = guilds.get(0);
// Vanity url
if (config.attemptToUseVanityUrl) {
String vanityUrl = guild.getVanityUrl();
if (vanityUrl != null) {
this.invite = vanityUrl;
return;
}
}
// Auto create
if (config.autoCreateInvite) {
Member selfMember = guild.getSelfMember();
if (!selfMember.hasPermission(Permission.CREATE_INSTANT_INVITE)) {
return;
}
TextChannel channel = guild.getRulesChannel();
if (channel == null) {
channel = guild.getDefaultChannel();
}
if (channel == null) {
return;
}
channel.createInvite().setMaxAge(0).setUnique(true).queue(inv -> this.invite = inv.getUrl());
}
}
@Placeholder("discord_invite")
public String getInvite() {
return invite != null ? invite : UNKNOWN_INVITE;
}
}

View File

@ -31,7 +31,6 @@ public class StorageLinker extends CachedLinkProvider implements LinkProvider, L
public StorageLinker(DiscordSRV discordSRV) { public StorageLinker(DiscordSRV discordSRV) {
super(discordSRV); super(discordSRV);
discordSRV.logger().info("Using storage for linked accounts");
} }
@Override @Override

View File

@ -20,13 +20,16 @@ package com.discordsrv.common.module;
import com.discordsrv.api.event.bus.EventPriority; import com.discordsrv.api.event.bus.EventPriority;
import com.discordsrv.api.event.bus.Subscribe; import com.discordsrv.api.event.bus.Subscribe;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVReloadEvent;
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent; import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
import com.discordsrv.api.module.type.Module; import com.discordsrv.api.module.type.Module;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.debug.DebugGenerateEvent; import com.discordsrv.common.debug.DebugGenerateEvent;
import com.discordsrv.common.debug.file.TextDebugFile; import com.discordsrv.common.debug.file.TextDebugFile;
import com.discordsrv.common.function.CheckedFunction;
import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.NamedLogger;
import com.discordsrv.common.module.type.AbstractModule; import com.discordsrv.common.module.type.AbstractModule;
import com.discordsrv.common.module.type.ModuleDelegate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -39,13 +42,20 @@ public class ModuleManager {
private final Set<Module> modules = new CopyOnWriteArraySet<>(); private final Set<Module> modules = new CopyOnWriteArraySet<>();
private final Map<String, Module> moduleLookupTable = new ConcurrentHashMap<>(); private final Map<String, Module> moduleLookupTable = new ConcurrentHashMap<>();
private final Map<Module, AbstractModule<?>> delegates = new ConcurrentHashMap<>();
private final DiscordSRV discordSRV; private final DiscordSRV discordSRV;
private final Logger logger;
public ModuleManager(DiscordSRV discordSRV) { public ModuleManager(DiscordSRV discordSRV) {
this.discordSRV = discordSRV; this.discordSRV = discordSRV;
this.logger = new NamedLogger(discordSRV, "MODULE_MANAGER");
discordSRV.eventBus().subscribe(this); discordSRV.eventBus().subscribe(this);
} }
public Logger logger() {
return logger;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T extends Module> T getModule(Class<T> moduleType) { public <T extends Module> T getModule(Class<T> moduleType) {
return (T) moduleLookupTable.computeIfAbsent(moduleType.getName(), key -> { return (T) moduleLookupTable.computeIfAbsent(moduleType.getName(), key -> {
@ -62,37 +72,66 @@ public class ModuleManager {
}); });
} }
public void register(AbstractModule<?> module) { private AbstractModule<?> getAbstract(Module module) {
return module instanceof AbstractModule
? (AbstractModule<?>) module
: delegates.computeIfAbsent(module, mod -> new ModuleDelegate(discordSRV, mod));
}
public <DT extends DiscordSRV> void registerModule(DT discordSRV, CheckedFunction<DT, AbstractModule<?>> function) {
try {
register(function.apply(discordSRV));
} catch (Throwable t) {
logger.debug("Module initialization failed", t);
}
}
public void register(Module module) {
if (module instanceof ModuleDelegate) {
throw new IllegalArgumentException("Cannot register a delegate");
}
AbstractModule<?> abstractModule = getAbstract(module);
logger.debug(module + " registered");
this.modules.add(module); this.modules.add(module);
this.moduleLookupTable.put(module.getClass().getName(), module); this.moduleLookupTable.put(module.getClass().getName(), module);
enable(module, true); if (discordSRV.config() != null) {
// Check if config is ready, if it is already we'll enable the module
enable(abstractModule);
}
} }
private void enable(Module module, boolean enableNonAbstract) { private void enable(AbstractModule<?> module) {
try { try {
if (module instanceof AbstractModule) { if (module.enableModule()) {
((AbstractModule<?>) module).enableModule(); logger.debug(module + " enabled");
} else if (enableNonAbstract) {
module.enable();
} }
} catch (Throwable t) { } catch (Throwable t) {
discordSRV.logger().error("Failed to enable " + module.getClass().getSimpleName(), t); discordSRV.logger().error("Failed to enable " + module.toString(), t);
} }
} }
public void unregister(Module module) { public void unregister(Module module) {
this.modules.remove(module); if (module instanceof ModuleDelegate) {
this.moduleLookupTable.values().removeIf(mod -> mod == module); throw new IllegalArgumentException("Cannot unregister a delegate");
}
disable(module); disable(module);
this.modules.remove(module);
this.moduleLookupTable.values().removeIf(mod -> mod == module);
this.delegates.remove(module);
} }
private void disable(Module module) { private void disable(Module module) {
AbstractModule<?> abstractModule = getAbstract(module);
try { try {
module.disable(); logger.debug(module + " disabling");
abstractModule.disable();
} catch (Throwable t) { } catch (Throwable t) {
discordSRV.logger().error("Failed to disable " + module.getClass().getSimpleName(), t); discordSRV.logger().error("Failed to disable " + abstractModule.toString(), t);
} }
} }
@ -103,14 +142,15 @@ public class ModuleManager {
} }
} }
@Subscribe(priority = EventPriority.EARLY) public void reload() {
public void onReload(DiscordSRVReloadEvent event) {
for (Module module : modules) { for (Module module : modules) {
AbstractModule<?> abstractModule = getAbstract(module);
// Check if the module needs to be enabled due to reload // Check if the module needs to be enabled due to reload
enable(module, false); enable(abstractModule);
try { try {
module.reload(); abstractModule.reload();
} catch (Throwable t) { } catch (Throwable t) {
discordSRV.logger().error("Failed to reload " + module.getClass().getSimpleName(), t); discordSRV.logger().error("Failed to reload " + module.getClass().getSimpleName(), t);
} }
@ -124,11 +164,13 @@ public class ModuleManager {
builder.append("Enabled modules:"); builder.append("Enabled modules:");
List<Module> disabled = new ArrayList<>(); List<Module> disabled = new ArrayList<>();
for (Module module : modules) { for (Module module : modules) {
if (!module.isEnabled()) { AbstractModule<?> abstractModule = getAbstract(module);
disabled.add(module);
if (!abstractModule.isEnabled()) {
disabled.add(abstractModule);
continue; continue;
} }
appendModule(builder, module); appendModule(builder, abstractModule);
} }
builder.append("\n\nDisabled modules:"); builder.append("\n\nDisabled modules:");

View File

@ -44,9 +44,9 @@ public abstract class AbstractModule<DT extends DiscordSRV> implements Module {
return logger; return logger;
} }
public final void enableModule() { public final boolean enableModule() {
if (hasBeenEnabled || !isEnabled()) { if (hasBeenEnabled || !isEnabled()) {
return; return false;
} }
hasBeenEnabled = true; hasBeenEnabled = true;
@ -56,6 +56,12 @@ public abstract class AbstractModule<DT extends DiscordSRV> implements Module {
discordSRV.eventBus().subscribe(this); discordSRV.eventBus().subscribe(this);
// Ignore not having listener methods exception // Ignore not having listener methods exception
} catch (IllegalArgumentException ignored) {} } catch (IllegalArgumentException ignored) {}
return true;
}
@Override
public String toString() {
return getClass().getName() + "{enabled=" + isEnabled() + "}";
} }
// Utility // Utility

View File

@ -0,0 +1,59 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.module.type;
import com.discordsrv.api.module.type.Module;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.logging.NamedLogger;
public class ModuleDelegate extends AbstractModule<DiscordSRV> {
private final Module module;
public ModuleDelegate(DiscordSRV discordSRV, Module module) {
super(discordSRV, new NamedLogger(discordSRV, module.getClass().getName()));
this.module = module;
}
@Override
public boolean isEnabled() {
return module.isEnabled();
}
@Override
public void enable() {
module.enable();
}
@Override
public void reload() {
module.reload();
}
@Override
public void disable() {
module.disable();
}
@Override
public String toString() {
String original = super.toString();
return original.substring(0, original.length() - 1) + ",module=" + module.getClass().getName() + "(" + module + ")}";
}
}

View File

@ -18,6 +18,7 @@
package com.discordsrv.common; package com.discordsrv.common;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.connection.ConnectionConfig; import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig; import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager; import com.discordsrv.common.config.manager.ConnectionConfigManager;
@ -38,6 +39,7 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@SuppressWarnings("ConstantConditions")
public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionConfig> { public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionConfig> {
public static final MockDiscordSRV INSTANCE = new MockDiscordSRV(); public static final MockDiscordSRV INSTANCE = new MockDiscordSRV();
@ -102,6 +104,11 @@ public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionCon
return null; return null;
} }
@Override
public ICommandHandler commandHandler() {
return null;
}
@Override @Override
public @NotNull AbstractPlayerProvider<?, ?> playerProvider() { public @NotNull AbstractPlayerProvider<?, ?> playerProvider() {
return null; return null;

View File

@ -19,6 +19,7 @@
package com.discordsrv.config; package com.discordsrv.config;
import com.discordsrv.common.AbstractDiscordSRV; import com.discordsrv.common.AbstractDiscordSRV;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.connection.ConnectionConfig; import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig; import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager; import com.discordsrv.common.config.manager.ConnectionConfigManager;
@ -35,6 +36,7 @@ import org.jetbrains.annotations.NotNull;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@SuppressWarnings("ConstantConditions")
public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionConfig> { public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionConfig> {
@Override @Override
@ -77,6 +79,11 @@ public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionCon
return null; return null;
} }
@Override
public ICommandHandler commandHandler() {
return null;
}
@Override @Override
public @NotNull AbstractPlayerProvider<?, ?> playerProvider() { public @NotNull AbstractPlayerProvider<?, ?> playerProvider() {
return null; return null;

View File

@ -1,5 +1,11 @@
apply from: rootProject.file('buildscript/runtime.gradle') apply from: rootProject.file('buildscript/runtime.gradle')
shadowJar {
archiveFileName = 'sponge.jarinjar'
// Relocations in buildscript/relocations.gradle
}
var spongeVersion = '8.0.0' var spongeVersion = '8.0.0'
allprojects { allprojects {
repositories { repositories {
@ -31,9 +37,3 @@ dependencies {
implementation 'dev.vankka:dependencydownload-jarinjar-bootstrap:' + rootProject.ddVersion implementation 'dev.vankka:dependencydownload-jarinjar-bootstrap:' + rootProject.ddVersion
compileOnlyApi project(':sponge:sponge-loader') compileOnlyApi project(':sponge:sponge-loader')
} }
shadowJar {
archiveFileName = 'sponge.jarinjar'
// Relocations in buildscript/relocations.gradle
}

View File

@ -22,6 +22,7 @@ import com.discordsrv.common.dependency.InitialDependencyLoader;
import com.discordsrv.common.logging.Logger; import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl; import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl;
import com.discordsrv.sponge.bootstrap.ISpongeBootstrap; import com.discordsrv.sponge.bootstrap.ISpongeBootstrap;
import com.discordsrv.sponge.command.game.handler.SpongeCommandHandler;
import dev.vankka.dependencydownload.classpath.ClasspathAppender; import dev.vankka.dependencydownload.classpath.ClasspathAppender;
import dev.vankka.mcdependencydownload.bootstrap.AbstractBootstrap; import dev.vankka.mcdependencydownload.bootstrap.AbstractBootstrap;
import dev.vankka.mcdependencydownload.bootstrap.classpath.JarInJarClasspathAppender; import dev.vankka.mcdependencydownload.bootstrap.classpath.JarInJarClasspathAppender;
@ -39,6 +40,7 @@ public class DiscordSRVSpongeBootstrap extends AbstractBootstrap implements ISpo
private final ClasspathAppender classpathAppender; private final ClasspathAppender classpathAppender;
private final InitialDependencyLoader dependencies; private final InitialDependencyLoader dependencies;
private SpongeDiscordSRV discordSRV; private SpongeDiscordSRV discordSRV;
private SpongeCommandHandler commandHandler;
private final PluginContainer pluginContainer; private final PluginContainer pluginContainer;
private final Game game; private final Game game;
@ -63,11 +65,14 @@ public class DiscordSRVSpongeBootstrap extends AbstractBootstrap implements ISpo
@Override @Override
public void onConstruct() { public void onConstruct() {
dependencies.load(); dependencies.load();
this.commandHandler = new SpongeCommandHandler(() -> discordSRV, pluginContainer);
game.eventManager().registerListeners(pluginContainer, commandHandler);
} }
@Override @Override
public void onStarted() { public void onStarted() {
dependencies.enable(() -> this.discordSRV = new SpongeDiscordSRV(logger, classpathAppender, dataDirectory, pluginContainer, game)); dependencies.enable(() -> this.discordSRV = new SpongeDiscordSRV(logger, classpathAppender, dataDirectory, pluginContainer, game, commandHandler));
if (discordSRV != null) { if (discordSRV != null) {
discordSRV.invokeServerStarted(); discordSRV.invokeServerStarted();
} }

View File

@ -19,6 +19,7 @@
package com.discordsrv.sponge; package com.discordsrv.sponge;
import com.discordsrv.api.DiscordSRVApi; import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.connection.ConnectionConfig; import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig; import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager; import com.discordsrv.common.config.manager.ConnectionConfigManager;
@ -27,6 +28,7 @@ import com.discordsrv.common.debug.data.OnlineMode;
import com.discordsrv.common.logging.Logger; import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.plugin.PluginManager; import com.discordsrv.common.plugin.PluginManager;
import com.discordsrv.common.server.ServerDiscordSRV; import com.discordsrv.common.server.ServerDiscordSRV;
import com.discordsrv.sponge.command.game.handler.SpongeCommandHandler;
import com.discordsrv.sponge.console.SpongeConsole; import com.discordsrv.sponge.console.SpongeConsole;
import com.discordsrv.sponge.player.SpongePlayerProvider; import com.discordsrv.sponge.player.SpongePlayerProvider;
import com.discordsrv.sponge.plugin.SpongePluginManager; import com.discordsrv.sponge.plugin.SpongePluginManager;
@ -52,8 +54,16 @@ public class SpongeDiscordSRV extends ServerDiscordSRV<MainConfig, ConnectionCon
private final SpongeConsole console; private final SpongeConsole console;
private final SpongePlayerProvider playerProvider; private final SpongePlayerProvider playerProvider;
private final SpongePluginManager pluginManager; private final SpongePluginManager pluginManager;
private final SpongeCommandHandler commandHandler;
public SpongeDiscordSRV(Logger logger, ClasspathAppender classpathAppender, Path dataDirectory, PluginContainer pluginContainer, Game game) { public SpongeDiscordSRV(
Logger logger,
ClasspathAppender classpathAppender,
Path dataDirectory,
PluginContainer pluginContainer,
Game game,
SpongeCommandHandler commandHandler
) {
this.logger = logger; this.logger = logger;
this.classpathAppender = classpathAppender; this.classpathAppender = classpathAppender;
this.dataDirectory = dataDirectory; this.dataDirectory = dataDirectory;
@ -64,6 +74,7 @@ public class SpongeDiscordSRV extends ServerDiscordSRV<MainConfig, ConnectionCon
this.console = new SpongeConsole(this); this.console = new SpongeConsole(this);
this.playerProvider = new SpongePlayerProvider(this); this.playerProvider = new SpongePlayerProvider(this);
this.pluginManager = new SpongePluginManager(this); this.pluginManager = new SpongePluginManager(this);
this.commandHandler = commandHandler;
load(); load();
} }
@ -123,6 +134,11 @@ public class SpongeDiscordSRV extends ServerDiscordSRV<MainConfig, ConnectionCon
return classpathAppender; return classpathAppender;
} }
@Override
public ICommandHandler commandHandler() {
return commandHandler;
}
@Override @Override
public ConnectionConfigManager<ConnectionConfig> connectionConfigManager() { public ConnectionConfigManager<ConnectionConfig> connectionConfigManager() {
return null; return null;

View File

@ -0,0 +1,145 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.sponge.command.game.handler;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.sponge.SpongeDiscordSRV;
import com.discordsrv.sponge.command.game.sender.SpongeCommandSender;
import net.kyori.adventure.audience.Audience;
import org.spongepowered.api.SystemSubject;
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.command.parameter.managed.Flag;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.lifecycle.RegisterCommandEvent;
import org.spongepowered.api.service.permission.Subject;
import org.spongepowered.plugin.PluginContainer;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
/**
* Sponge has its own api for interacting with Brigadier...
*/
public class SpongeCommandHandler implements ICommandHandler {
private final Map<String, Command.Parameterized> commands = new LinkedHashMap<>();
private final Supplier<SpongeDiscordSRV> discordSRV;
private final PluginContainer container;
public SpongeCommandHandler(Supplier<SpongeDiscordSRV> discordSRV, PluginContainer container) {
this.discordSRV = discordSRV;
this.container = container;
}
@Override
public void registerCommand(GameCommand command) {
commands.put(command.getLabel(), remap(command));
}
private ICommandSender getSender(Subject subject, Audience audience) {
SpongeDiscordSRV discordSRV = this.discordSRV.get();
if (discordSRV != null) {
if (subject instanceof ServerPlayer) {
return discordSRV.playerProvider().player((ServerPlayer) subject);
} else if (subject instanceof SystemSubject) {
return discordSRV.console();
}
}
return new SpongeCommandSender(discordSRV, () -> subject, () -> audience);
}
private ICommandSender getSender(CommandContext context) {
return getSender(context.subject(), context.cause().audience());
}
private Command.Parameterized remap(GameCommand command) {
GameCommand redirection = command.getRedirection();
if (redirection != null) {
command = redirection;
}
//GameCommandSuggester suggester = command.getSuggester();
GameCommandExecutor executor = command.getExecutor();
String permission = command.getRequiredPermission();
Command.Builder builder = Command.builder();
builder.addFlag(Flag.builder().alias(command.getLabel()).build());
if (permission != null) {
builder.permission(permission);
}
for (GameCommand child : command.getChildren()) {
builder.addChild(remap(child));
}
if (executor != null) {
builder.executor(context -> {
executor.execute(getSender(context), new GameCommandArguments() {
@Override
public <T> T get(String label, Class<T> type) {
return context.one(new Parameter.Key<T>() {
@Override
public String key() {
return label;
}
@Override
public Type type() {
return type;
}
@Override
public boolean isInstance(Object value) {
return type.isInstance(value);
}
@SuppressWarnings("unchecked")
@Override
public T cast(Object value) {
return (T) value;
}
}).orElse(null);
}
});
return CommandResult.success();
});
}
return builder.build();
}
@Listener
public void onRegister(RegisterCommandEvent<Command.Parameterized> event) {
for (Map.Entry<String, Command.Parameterized> entry : commands.entrySet()) {
event.register(container, entry.getValue(), entry.getKey());
}
}
}

View File

@ -0,0 +1,59 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.sponge.command.game.sender;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.sponge.SpongeDiscordSRV;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.command.exception.CommandException;
import org.spongepowered.api.service.permission.Subject;
import java.util.function.Supplier;
public class SpongeCommandSender implements ICommandSender {
protected final SpongeDiscordSRV discordSRV;
protected final Supplier<Subject> subjectSupplier;
protected final Supplier<Audience> audienceSupplier;
public SpongeCommandSender(SpongeDiscordSRV discordSRV, Supplier<Subject> subjectSupplier, Supplier<Audience> audienceSupplier) {
this.discordSRV = discordSRV;
this.subjectSupplier = subjectSupplier;
this.audienceSupplier = audienceSupplier;
}
@Override
public boolean hasPermission(String permission) {
return subjectSupplier.get().hasPermission(permission);
}
@Override
public void runCommand(String command) {
try {
Subject subject = subjectSupplier.get();
discordSRV.game().server().commandManager().process((Subject & Audience) subject, command);
} catch (CommandException ignored) {}
}
@Override
public @NotNull Audience audience() {
return audienceSupplier.get();
}
}

View File

@ -22,40 +22,19 @@ import com.discordsrv.common.console.Console;
import com.discordsrv.common.logging.backend.LoggingBackend; import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl; import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl;
import com.discordsrv.sponge.SpongeDiscordSRV; import com.discordsrv.sponge.SpongeDiscordSRV;
import net.kyori.adventure.audience.Audience; import com.discordsrv.sponge.command.game.sender.SpongeCommandSender;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.command.exception.CommandException;
public class SpongeConsole implements Console { public class SpongeConsole extends SpongeCommandSender implements Console {
private final SpongeDiscordSRV discordSRV;
private final LoggingBackend loggingBackend; private final LoggingBackend loggingBackend;
public SpongeConsole(SpongeDiscordSRV discordSRV) { public SpongeConsole(SpongeDiscordSRV discordSRV) {
this.discordSRV = discordSRV; super(discordSRV, () -> discordSRV.game().systemSubject(), () -> discordSRV.game().systemSubject());
this.loggingBackend = Log4JLoggerImpl.getRoot(); this.loggingBackend = Log4JLoggerImpl.getRoot();
} }
@Override
public boolean hasPermission(String permission) {
return discordSRV.game().systemSubject().hasPermission(permission);
}
@Override
public void runCommand(String command) {
try {
discordSRV.game().server().commandManager().process(
discordSRV.game().systemSubject(), command);
} catch (CommandException ignored) {}
}
@Override @Override
public LoggingBackend loggingBackend() { public LoggingBackend loggingBackend() {
return loggingBackend; return loggingBackend;
} }
@Override
public @NotNull Audience audience() {
return discordSRV.game().systemSubject();
}
} }

View File

@ -21,31 +21,21 @@ package com.discordsrv.sponge.player;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.player.IPlayer; import com.discordsrv.common.player.IPlayer;
import com.discordsrv.sponge.SpongeDiscordSRV; import com.discordsrv.sponge.SpongeDiscordSRV;
import net.kyori.adventure.audience.Audience; import com.discordsrv.sponge.command.game.sender.SpongeCommandSender;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.command.exception.CommandException;
import org.spongepowered.api.entity.living.player.server.ServerPlayer; import org.spongepowered.api.entity.living.player.server.ServerPlayer;
public class SpongePlayer extends SpongeOfflinePlayer implements IPlayer { public class SpongePlayer extends SpongeCommandSender implements IPlayer {
private final ServerPlayer player; private final ServerPlayer player;
private final Identity identity;
public SpongePlayer(SpongeDiscordSRV discordSRV, ServerPlayer player) { public SpongePlayer(SpongeDiscordSRV discordSRV, ServerPlayer player) {
super(discordSRV, player.user()); super(discordSRV, () -> player, () -> player);
this.player = player; this.player = player;
} this.identity = Identity.identity(player.uniqueId());
@Override
public boolean hasPermission(String permission) {
return player.hasPermission(permission);
}
@Override
public void runCommand(String command) {
try {
discordSRV.game().server().commandManager().process(player, command);
} catch (CommandException ignored) {}
} }
@Override @Override
@ -53,13 +43,18 @@ public class SpongePlayer extends SpongeOfflinePlayer implements IPlayer {
return discordSRV; return discordSRV;
} }
@Override
public @NotNull String username() {
return player.name();
}
@Override @Override
public @NotNull Component displayName() { public @NotNull Component displayName() {
return player.displayName().get(); return player.displayName().get();
} }
@Override @Override
public @NotNull Audience audience() { public @NotNull Identity identity() {
return player; return identity;
} }
} }

View File

@ -21,7 +21,6 @@ package com.discordsrv.sponge.player;
import com.discordsrv.common.player.IOfflinePlayer; import com.discordsrv.common.player.IOfflinePlayer;
import com.discordsrv.common.server.player.ServerPlayerProvider; import com.discordsrv.common.server.player.ServerPlayerProvider;
import com.discordsrv.sponge.SpongeDiscordSRV; import com.discordsrv.sponge.SpongeDiscordSRV;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.living.player.User; import org.spongepowered.api.entity.living.player.User;
import org.spongepowered.api.entity.living.player.server.ServerPlayer; import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.Listener;
@ -64,7 +63,7 @@ public class SpongePlayerProvider extends ServerPlayerProvider<SpongePlayer, Spo
removePlayer(event.player().uniqueId()); removePlayer(event.player().uniqueId());
} }
public SpongePlayer player(Player player) { public SpongePlayer player(ServerPlayer player) {
return player(player.uniqueId()).orElseThrow(() -> new IllegalStateException("Player not available")); return player(player.uniqueId()).orElseThrow(() -> new IllegalStateException("Player not available"));
} }

View File

@ -1,6 +1,12 @@
apply from: rootProject.file('buildscript/standalone.gradle') apply from: rootProject.file('buildscript/standalone.gradle')
apply plugin: 'net.kyori.blossom' apply plugin: 'net.kyori.blossom'
shadowJar {
archiveBaseName = 'DiscordSRV-Velocity'
// Relocations in buildscript/relocations.gradle
}
repositories { repositories {
exclusiveContent { exclusiveContent {
forRepository { forRepository {
@ -33,12 +39,6 @@ jar {
dependsOn blossomSourceReplacementJava dependsOn blossomSourceReplacementJava
} }
shadowJar {
archiveBaseName = 'DiscordSRV-Velocity'
// Relocations in buildscript/relocations.gradle
}
blossom { blossom {
var mainClass = 'src/main/java/com/discordsrv/velocity/DiscordSRVVelocityBootstrap.java' var mainClass = 'src/main/java/com/discordsrv/velocity/DiscordSRVVelocityBootstrap.java'
replaceToken('@VERSION@', project.version, mainClass) replaceToken('@VERSION@', project.version, mainClass)

View File

@ -18,6 +18,7 @@
package com.discordsrv.velocity; package com.discordsrv.velocity;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.config.connection.ConnectionConfig; import com.discordsrv.common.config.connection.ConnectionConfig;
import com.discordsrv.common.config.main.MainConfig; import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.manager.ConnectionConfigManager; import com.discordsrv.common.config.manager.ConnectionConfigManager;
@ -27,6 +28,7 @@ import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.plugin.PluginManager; import com.discordsrv.common.plugin.PluginManager;
import com.discordsrv.common.scheduler.StandardScheduler; import com.discordsrv.common.scheduler.StandardScheduler;
import com.discordsrv.proxy.ProxyDiscordSRV; import com.discordsrv.proxy.ProxyDiscordSRV;
import com.discordsrv.velocity.command.game.handler.VelocityCommandHandler;
import com.discordsrv.velocity.console.VelocityConsole; import com.discordsrv.velocity.console.VelocityConsole;
import com.discordsrv.velocity.player.VelocityPlayerProvider; import com.discordsrv.velocity.player.VelocityPlayerProvider;
import com.discordsrv.velocity.plugin.VelocityPluginManager; import com.discordsrv.velocity.plugin.VelocityPluginManager;
@ -50,6 +52,7 @@ public class VelocityDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionCo
private final VelocityConsole console; private final VelocityConsole console;
private final VelocityPlayerProvider playerProvider; private final VelocityPlayerProvider playerProvider;
private final VelocityPluginManager pluginManager; private final VelocityPluginManager pluginManager;
private final VelocityCommandHandler commandHandler;
public VelocityDiscordSRV(Object plugin, Logger logger, ClasspathAppender classpathAppender, ProxyServer proxyServer, PluginContainer pluginContainer, Path dataDirectory) { public VelocityDiscordSRV(Object plugin, Logger logger, ClasspathAppender classpathAppender, ProxyServer proxyServer, PluginContainer pluginContainer, Path dataDirectory) {
this.plugin = plugin; this.plugin = plugin;
@ -63,6 +66,7 @@ public class VelocityDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionCo
this.console = new VelocityConsole(this); this.console = new VelocityConsole(this);
this.playerProvider = new VelocityPlayerProvider(this); this.playerProvider = new VelocityPlayerProvider(this);
this.pluginManager = new VelocityPluginManager(this); this.pluginManager = new VelocityPluginManager(this);
this.commandHandler = new VelocityCommandHandler(this);
load(); load();
} }
@ -124,6 +128,11 @@ public class VelocityDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionCo
return classpathAppender; return classpathAppender;
} }
@Override
public ICommandHandler commandHandler() {
return commandHandler;
}
@Override @Override
public ConnectionConfigManager<ConnectionConfig> connectionConfigManager() { public ConnectionConfigManager<ConnectionConfig> connectionConfigManager() {
return null; return null;

View File

@ -0,0 +1,56 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.velocity.command.game.handler;
import com.discordsrv.common.command.game.abstraction.GameCommand;
import com.discordsrv.common.command.game.handler.ICommandHandler;
import com.discordsrv.common.command.game.handler.util.BrigadierUtil;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.velocity.VelocityDiscordSRV;
import com.discordsrv.velocity.command.game.sender.VelocityCommandSender;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.ConsoleCommandSource;
import com.velocitypowered.api.proxy.Player;
public class VelocityCommandHandler implements ICommandHandler {
private final VelocityDiscordSRV discordSRV;
public VelocityCommandHandler(VelocityDiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
private ICommandSender getSender(CommandSource source) {
if (source instanceof Player) {
return discordSRV.playerProvider().player((Player) source);
} else if (source instanceof ConsoleCommandSource) {
return discordSRV.console();
} else {
return new VelocityCommandSender(discordSRV, source);
}
}
@Override
public void registerCommand(GameCommand command) {
LiteralCommandNode<CommandSource> node = BrigadierUtil.convertToBrigadier(command, this::getSender);
discordSRV.proxy().getCommandManager().register(new BrigadierCommand(node));
}
}

View File

@ -0,0 +1,51 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.velocity.command.game.sender;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.velocity.VelocityDiscordSRV;
import com.velocitypowered.api.command.CommandSource;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
public class VelocityCommandSender implements ICommandSender {
protected final VelocityDiscordSRV discordSRV;
protected final CommandSource commandSource;
public VelocityCommandSender(VelocityDiscordSRV discordSRV, CommandSource commandSource) {
this.discordSRV = discordSRV;
this.commandSource = commandSource;
}
@Override
public boolean hasPermission(String permission) {
return commandSource.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getCommandManager().executeAsync(commandSource, command);
}
@Override
public @NotNull Audience audience() {
return commandSource;
}
}

View File

@ -22,37 +22,19 @@ import com.discordsrv.common.console.Console;
import com.discordsrv.common.logging.backend.LoggingBackend; import com.discordsrv.common.logging.backend.LoggingBackend;
import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl; import com.discordsrv.common.logging.backend.impl.Log4JLoggerImpl;
import com.discordsrv.velocity.VelocityDiscordSRV; import com.discordsrv.velocity.VelocityDiscordSRV;
import net.kyori.adventure.audience.Audience; import com.discordsrv.velocity.command.game.sender.VelocityCommandSender;
import org.jetbrains.annotations.NotNull;
public class VelocityConsole implements Console { public class VelocityConsole extends VelocityCommandSender implements Console {
private final VelocityDiscordSRV discordSRV;
private final LoggingBackend loggingBackend; private final LoggingBackend loggingBackend;
public VelocityConsole(VelocityDiscordSRV discordSRV) { public VelocityConsole(VelocityDiscordSRV discordSRV) {
this.discordSRV = discordSRV; super(discordSRV, discordSRV.proxy().getConsoleCommandSource());
this.loggingBackend = Log4JLoggerImpl.getRoot(); this.loggingBackend = Log4JLoggerImpl.getRoot();
} }
@Override
public boolean hasPermission(String permission) {
return discordSRV.proxy().getConsoleCommandSource().hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getCommandManager().executeAsync(
discordSRV.proxy().getConsoleCommandSource(), command);
}
@Override @Override
public LoggingBackend loggingBackend() { public LoggingBackend loggingBackend() {
return loggingBackend; return loggingBackend;
} }
@Override
public @NotNull Audience audience() {
return discordSRV.proxy().getConsoleCommandSource();
}
} }

View File

@ -21,32 +21,21 @@ package com.discordsrv.velocity.player;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.player.IPlayer; import com.discordsrv.common.player.IPlayer;
import com.discordsrv.velocity.VelocityDiscordSRV; import com.discordsrv.velocity.VelocityDiscordSRV;
import com.discordsrv.velocity.command.game.sender.VelocityCommandSender;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.identity.Identity; import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class VelocityPlayer implements IPlayer { public class VelocityPlayer extends VelocityCommandSender implements IPlayer {
private final VelocityDiscordSRV discordSRV;
private final Player player; private final Player player;
public VelocityPlayer(VelocityDiscordSRV discordSRV, Player player) { public VelocityPlayer(VelocityDiscordSRV discordSRV, Player player) {
this.discordSRV = discordSRV; super(discordSRV, player);
this.player = player; this.player = player;
} }
@Override
public boolean hasPermission(String permission) {
return player.hasPermission(permission);
}
@Override
public void runCommand(String command) {
discordSRV.proxy().getCommandManager().executeAsync(player, command);
}
@Override @Override
public DiscordSRV discordSRV() { public DiscordSRV discordSRV() {
return discordSRV; return discordSRV;
@ -70,9 +59,4 @@ public class VelocityPlayer implements IPlayer {
() -> Component.text(player.getUsername()) () -> Component.text(player.getUsername())
); );
} }
@Override
public @NotNull Audience audience() {
return player;
}
} }