LPInjector and mcchat fixes

Fixed LPInjector registering to the Core
Stop MCChatListener from having multiple active instances
MinecraftChatModule.state instead of all the flags
Showing MinecraftChatModule enable/disable on Discord
/discord reset --> restart
Wait for each shutdown message to send on shutdown (although it hasn't really been an issue so far)
This means using Mono<?> in a lot of places
Also added a contract (IntelliJ) to warn if not subscribed
Faking getOnlinePlayers() is unnecessary and causes too much trouble
Today's work
This commit is contained in:
Norbi Peti 2020-10-10 00:29:21 +02:00
parent ccc15aa048
commit a27a262858
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
17 changed files with 311 additions and 175 deletions

View file

@ -4,7 +4,7 @@ A plugin that provides Minecraft chat functionality and other features.
## Setup
This plugin needs Chroma-Core to work. If you have that and this plugin, start the server, and follow the instructions.
You'll need a Discord application made, and a bot account created for it.
You can restart the plugin using /discord reset without having to restart the whole server.
You can restart the plugin using /discord restart without having to restart the whole server.
## Building
Maven is used to build this project with all of its dependencies. You will need Spigot 1.12.2 and 1.14.4 built using BuildTools.

View file

@ -15,16 +15,12 @@ public class ChromaBot {
* May be null if it's not initialized. Initialization happens after the server is done loading (using {@link BukkitScheduler#runTaskAsynchronously(org.bukkit.plugin.Plugin, Runnable)})
*/
private static @Getter ChromaBot instance;
private DiscordPlugin dp;
/**
* This will set the instance field.
*
* @param dp The Discord plugin
*/
ChromaBot(DiscordPlugin dp) {
ChromaBot() {
instance = this;
this.dp = dp;
}
static void delete() {
@ -37,7 +33,7 @@ public class ChromaBot {
* @param message The message to send, duh (use {@link MessageChannel#createMessage(String)})
*/
public void sendMessage(Function<Mono<MessageChannel>, Mono<Message>> message) {
MCChatUtils.forAllMCChat(ch -> message.apply(ch).subscribe());
MCChatUtils.forPublicPrivateChat(message::apply).subscribe();
}
/**
@ -47,7 +43,7 @@ public class ChromaBot {
* @param toggle The toggle type for channelcon
*/
public void sendMessageCustomAsWell(Function<Mono<MessageChannel>, Mono<Message>> message, @Nullable ChannelconBroadcast toggle) {
MCChatUtils.forCustomAndAllMCChat(ch -> message.apply(ch).subscribe(), toggle, false);
MCChatUtils.forCustomAndAllMCChat(message::apply, toggle, false).subscribe();
}
public void updatePlayerList() {

View file

@ -8,6 +8,8 @@ import discord4j.core.object.entity.channel.MessageChannel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Delegate;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.*;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
@ -24,10 +26,8 @@ import org.mockito.MockSettings;
import org.mockito.Mockito;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.UUID;
import java.net.InetSocketAddress;
import java.util.*;
import static org.mockito.Answers.RETURNS_DEFAULTS;
@ -160,6 +160,11 @@ public abstract class DiscordConnectedPlayer extends DiscordSenderBase implement
location.getYaw(), location.getPitch());
}
@Override
public Location getEyeLocation() {
return getLocation();
}
@Override
@Deprecated
public double getMaxHealth() {
@ -222,6 +227,71 @@ public abstract class DiscordConnectedPlayer extends DiscordSenderBase implement
return GameMode.SPECTATOR;
}
private final Player.Spigot spigot = new Player.Spigot() {
@Override
public InetSocketAddress getRawAddress() {
return null;
}
@Override
public void playEffect(Location location, Effect effect, int id, int data, float offsetX, float offsetY, float offsetZ, float speed, int particleCount, int radius) {
}
@Override
public boolean getCollidesWithEntities() {
return false;
}
@Override
public void setCollidesWithEntities(boolean collides) {
}
@Override
public void respawn() {
}
@Override
public String getLocale() {
return "en_us";
}
@Override
public Set<Player> getHiddenPlayers() {
return Collections.emptySet();
}
@Override
public void sendMessage(BaseComponent component) {
DiscordConnectedPlayer.super.sendMessage(component.toPlainText());
}
@Override
public void sendMessage(BaseComponent... components) {
for (var component : components)
sendMessage(component);
}
@Override
public void sendMessage(ChatMessageType position, BaseComponent component) {
sendMessage(component); //Ignore position
}
@Override
public void sendMessage(ChatMessageType position, BaseComponent... components) {
sendMessage(components); //Ignore position
}
@Override
public boolean isInvulnerable() {
return true;
}
};
@Override
public Player.Spigot spigot() {
return spigot;
}
public static DiscordConnectedPlayer create(User user, MessageChannel channel, UUID uuid, String mcname,
MinecraftChatModule module) {
return Mockito.mock(DiscordConnectedPlayer.class,

View file

@ -3,6 +3,8 @@ package buttondevteam.discordplugin;
import buttondevteam.discordplugin.mcchat.MCChatPrivate;
import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.player.UserClass;
import discord4j.core.object.entity.User;
import discord4j.core.object.entity.channel.MessageChannel;
@UserClass(foldername = "discord")
public class DiscordPlayer extends ChromaGamerBase {
@ -20,7 +22,7 @@ public class DiscordPlayer extends ChromaGamerBase {
/**
* Returns true if player has the private Minecraft chat enabled. For setting the value, see
* {@link MCChatPrivate#privateMCChat(sx.blah.discord.handle.obj.MessageChannel, boolean, sx.blah.discord.handle.obj.User, DiscordPlayer)}
* {@link MCChatPrivate#privateMCChat(MessageChannel, boolean, User, DiscordPlayer)}
*/
public boolean isMinecraftChatEnabled() {
return MCChatPrivate.isMinecraftChatEnabled(this);

View file

@ -7,10 +7,10 @@ import buttondevteam.discordplugin.exceptions.ExceptionListenerModule;
import buttondevteam.discordplugin.fun.FunModule;
import buttondevteam.discordplugin.listeners.CommonListeners;
import buttondevteam.discordplugin.listeners.MCListener;
import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import buttondevteam.discordplugin.mccommands.DiscordMCCommand;
import buttondevteam.discordplugin.role.GameRoleModule;
import buttondevteam.discordplugin.util.DPState;
import buttondevteam.discordplugin.util.Timings;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ButtonPlugin;
@ -29,13 +29,10 @@ import discord4j.core.object.entity.Role;
import discord4j.core.object.presence.Activity;
import discord4j.core.object.presence.Presence;
import discord4j.core.object.reaction.ReactionEmoji;
import discord4j.rest.util.Color;
import discord4j.store.jdk.JdkStoreService;
import lombok.Getter;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.mockito.internal.util.MockUtil;
import reactor.core.publisher.Mono;
@ -43,7 +40,6 @@ import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@ButtonPlugin.ConfigOpts(disableConfigGen = true)
public class DiscordPlugin extends ButtonPlugin {
@ -118,7 +114,7 @@ public class DiscordPlugin extends ButtonPlugin {
getLogger().info("Initializing...");
plugin = this;
manager = new Command2DC();
registerCommand(new DiscordMCCommand()); //Register so that the reset command works
registerCommand(new DiscordMCCommand()); //Register so that the restart command works
String token;
File tokenFile = new File("TBMC", "Token.txt");
if (tokenFile.exists()) //Legacy support
@ -132,7 +128,7 @@ public class DiscordPlugin extends ButtonPlugin {
conf.set("token", "Token goes here");
conf.save(privateFile);
getLogger().severe("Token not found! Please set it in private.yml then do /discord reset");
getLogger().severe("Token not found! Please set it in private.yml then do /discord restart");
getLogger().severe("You need to have a bot account to use with your server.");
getLogger().severe("If you don't have one, go to https://discordapp.com/developers/applications/ and create an application, then create a bot for it and copy the bot token.");
return;
@ -152,7 +148,7 @@ public class DiscordPlugin extends ButtonPlugin {
}); /* All guilds have been received, client is fully connected */
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to enable the Discord plugin!", e, this);
getLogger().severe("You may be able to reset the plugin using /discord reset");
getLogger().severe("You may be able to restart the plugin using /discord restart");
}
}
@ -168,7 +164,7 @@ public class DiscordPlugin extends ButtonPlugin {
mainServer = mainServer().get().orElse(null); //Shouldn't change afterwards
if (mainServer == null) {
if (event.size() == 0) {
getLogger().severe("Main server not found! Invite the bot and do /discord reset");
getLogger().severe("Main server not found! Invite the bot and do /discord restart");
dc.getApplicationInfo().subscribe(info ->
getLogger().severe("Click here: https://discordapp.com/oauth2/authorize?client_id=" + info.getId().asString() + "&scope=bot&permissions=268509264"));
saveConfig(); //Put default there
@ -182,40 +178,6 @@ public class DiscordPlugin extends ButtonPlugin {
DPUtils.disableIfConfigErrorRes(null, commandChannel(), DPUtils.getMessageChannel(commandChannel()));
//Won't disable, just prints the warning here
Component.registerComponent(this, new GeneralEventBroadcasterModule());
Component.registerComponent(this, new MinecraftChatModule());
Component.registerComponent(this, new ExceptionListenerModule());
Component.registerComponent(this, new GameRoleModule()); //Needs the mainServer to be set
Component.registerComponent(this, new AnnouncerModule());
Component.registerComponent(this, new FunModule());
new ChromaBot(this).updatePlayerList(); //Initialize ChromaBot - The MCChatModule is tested to be enabled
getManager().registerCommand(new VersionCommand());
getManager().registerCommand(new UserinfoCommand());
getManager().registerCommand(new HelpCommand());
getManager().registerCommand(new DebugCommand());
getManager().registerCommand(new ConnectCommand());
if (DiscordMCCommand.resetting) //These will only execute if the chat is enabled
ChromaBot.getInstance().sendMessageCustomAsWell(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(Color.CYAN)
.setTitle("Discord plugin restarted - chat connected."))), ChannelconBroadcast.RESTART); //Really important to note the chat, hmm
else if (getConfig().getBoolean("serverup", false)) {
ChromaBot.getInstance().sendMessageCustomAsWell(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(Color.YELLOW)
.setTitle("Server recovered from a crash - chat connected."))), ChannelconBroadcast.RESTART);
val thr = new Throwable(
"The server shut down unexpectedly. See the log of the previous run for more details.");
thr.setStackTrace(new StackTraceElement[0]);
TBMCCoreAPI.SendException("The server crashed!", thr, this);
} else
ChromaBot.getInstance().sendMessageCustomAsWell(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(Color.GREEN)
.setTitle("Server started - chat connected."))), ChannelconBroadcast.RESTART);
DiscordMCCommand.resetting = false; //This is the last event handling this flag
getConfig().set("serverup", true);
saveConfig();
TBMCCoreAPI.SendUnsentExceptions();
TBMCCoreAPI.SendUnsentDebugMessages();
CommonListeners.register(dc.getEventDispatcher());
TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), this);
TBMCCoreAPI.RegisterUserClass(DiscordPlayer.class);
@ -223,6 +185,25 @@ public class DiscordPlugin extends ButtonPlugin {
? ((DiscordSenderBase) sender).getChromaUser() : null));
IHaveConfig.pregenConfig(this, null);
var cb = new ChromaBot(); //Initialize ChromaBot
Component.registerComponent(this, new GeneralEventBroadcasterModule());
Component.registerComponent(this, new MinecraftChatModule());
Component.registerComponent(this, new ExceptionListenerModule());
Component.registerComponent(this, new GameRoleModule()); //Needs the mainServer to be set
Component.registerComponent(this, new AnnouncerModule());
Component.registerComponent(this, new FunModule());
cb.updatePlayerList(); //The MCChatModule is tested to be enabled
getManager().registerCommand(new VersionCommand());
getManager().registerCommand(new UserinfoCommand());
getManager().registerCommand(new HelpCommand());
getManager().registerCommand(new DebugCommand());
getManager().registerCommand(new ConnectCommand());
TBMCCoreAPI.SendUnsentExceptions();
TBMCCoreAPI.SendUnsentDebugMessages();
if (!TBMCCoreAPI.IsTestServer()) {
dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe();
} else {
@ -234,46 +215,24 @@ public class DiscordPlugin extends ButtonPlugin {
}
}
/**
* Always true, except when running "stop" from console
*/
public static boolean Restart;
@Override
public void pluginPreDisable() {
if (ChromaBot.getInstance() == null) return; //Failed to load
Timings timings = new Timings();
timings.printElapsed("Disable start");
MCChatUtils.forCustomAndAllMCChat(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> {
timings.printElapsed("Sending message to " + ch.getMention());
if (DiscordMCCommand.resetting)
ecs.setColor(Color.ORANGE).setTitle("Discord plugin restarting");
else
ecs.setColor(Restart ? Color.ORANGE : Color.RED)
.setTitle(Restart ? "Server restarting" : "Server stopping")
.setDescription(
Bukkit.getOnlinePlayers().size() > 0
? (DPUtils
.sanitizeString(Bukkit.getOnlinePlayers().stream()
.map(Player::getDisplayName).collect(Collectors.joining(", ")))
+ (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ")
+ "thrown out") //TODO: Make configurable
: ""); //If 'restart' is disabled then this isn't shown even if joinleave is enabled
})).subscribe(), ChannelconBroadcast.RESTART, false);
timings.printElapsed("Updating player list");
ChromaBot.getInstance().updatePlayerList();
timings.printElapsed("Done");
if (MinecraftChatModule.state == DPState.RUNNING)
MinecraftChatModule.state = DPState.STOPPING_SERVER;
}
@Override
public void pluginDisable() {
Timings timings = new Timings();
timings.printElapsed("Actual disable start (logout)");
timings.printElapsed("Config setup");
getConfig().set("serverup", false);
if (ChromaBot.getInstance() == null) return; //Failed to load
saveConfig();
try {
SafeMode = true; // Stop interacting with Discord
ChromaBot.delete();

View file

@ -82,6 +82,7 @@ public class AnnouncerModule extends Component<DiscordPlugin> {
while (!stop) {
try {
if (!isEnabled()) {
//noinspection BusyWait
Thread.sleep(10000);
continue;
}
@ -135,6 +136,7 @@ public class AnnouncerModule extends Component<DiscordPlugin> {
e.printStackTrace();
}
try {
//noinspection BusyWait
Thread.sleep(10000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();

View file

@ -144,7 +144,7 @@ public class PlayerListWatcher {
if (packet.getClass() == ppoc) {
Field msgf = ppoc.getDeclaredField("a");
msgf.setAccessible(true);
MCChatUtils.forAllMCChat(MCChatUtils.send((String) toPlainText.invoke(msgf.get(packet))));
MCChatUtils.forPublicPrivateChat(MCChatUtils.send((String) toPlainText.invoke(msgf.get(packet)))).subscribe();
}
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to broadcast message sent to all players - hacking failed.", e, module);

View file

@ -3,6 +3,8 @@ package buttondevteam.discordplugin.listeners;
import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.commands.ConnectCommand;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import buttondevteam.discordplugin.util.DPState;
import buttondevteam.lib.TBMCCommandPreprocessEvent;
import buttondevteam.lib.player.TBMCPlayerGetInfoEvent;
import buttondevteam.lib.player.TBMCPlayerJoinEvent;
@ -53,6 +55,9 @@ public class MCListener implements Listener {
@EventHandler
public void onCommandPreprocess(TBMCCommandPreprocessEvent e) {
DiscordPlugin.Restart = !e.getMessage().equalsIgnoreCase("/stop"); // The variable is always true except if stopped
if (e.getMessage().equalsIgnoreCase("/stop"))
MinecraftChatModule.state = DPState.STOPPING_SERVER;
else
MinecraftChatModule.state = DPState.RESTARTING_SERVER;
}
}

View file

@ -45,8 +45,9 @@ public class MCChatListener implements Listener {
private BukkitTask sendtask;
private final LinkedBlockingQueue<AbstractMap.SimpleEntry<TBMCChatEvent, Instant>> sendevents = new LinkedBlockingQueue<>();
private Runnable sendrunnable;
private static Thread sendthread;
private Thread sendthread;
private final MinecraftChatModule module;
private boolean stop = false; //A new instance will be created on enable
public MCChatListener(MinecraftChatModule minecraftChatModule) {
module = minecraftChatModule;
@ -62,7 +63,7 @@ public class MCChatListener implements Listener {
sendrunnable = () -> {
sendthread = Thread.currentThread();
processMCToDiscord();
if (DiscordPlugin.plugin.isEnabled()) //Don't run again if shutting down
if (DiscordPlugin.plugin.isEnabled() && !stop) //Don't run again if shutting down
sendtask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable);
};
sendtask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable);
@ -188,12 +189,14 @@ public class MCChatListener implements Listener {
// The maps may not contain the senders for UnconnectedSenders
/**
* Stop the listener. Any calls to onMCChat will restart it as long as we're not in safe mode.
* Stop the listener permanently. Enabling the module will create a new instance.
*
* @param wait Wait 5 seconds for the threads to stop
*/
public static void stop(boolean wait) {
public void stop(boolean wait) {
stop = true;
MCChatPrivate.logoutAll();
MCChatUtils.LoggedInPlayers.clear();
if (sendthread != null) sendthread.interrupt();
if (recthread != null) recthread.interrupt();
try {
@ -221,7 +224,7 @@ public class MCChatListener implements Listener {
private BukkitTask rectask;
private final LinkedBlockingQueue<MessageCreateEvent> recevents = new LinkedBlockingQueue<>();
private Runnable recrun;
private static Thread recthread;
private Thread recthread;
// Discord
public Mono<Boolean> handleDiscord(MessageCreateEvent ev) {
@ -257,7 +260,7 @@ public class MCChatListener implements Listener {
recrun = () -> { //Don't return in a while loop next time
recthread = Thread.currentThread();
processDiscordToMC();
if (DiscordPlugin.plugin.isEnabled()) //Don't run again if shutting down
if (DiscordPlugin.plugin.isEnabled() && !stop) //Don't run again if shutting down
rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Continue message processing
};
rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Start message processing

View file

@ -3,6 +3,8 @@ package buttondevteam.discordplugin.mcchat;
import buttondevteam.core.ComponentManager;
import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.DiscordSenderBase;
import buttondevteam.lib.player.TBMCPlayer;
import discord4j.core.object.entity.User;
import discord4j.core.object.entity.channel.MessageChannel;
@ -35,11 +37,13 @@ public class MCChatPrivate {
} else {
val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user);
assert sender != null;
MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId());
if (p == null // Player is offline - If the player is online, that takes precedence
&& sender.isLoggedIn()) //Don't call the quit event if login failed
MCChatUtils.callLogoutEvent(sender, true);
sender.setLoggedIn(false);
Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> {
if ((p == null || p instanceof DiscordSenderBase) // Player is offline - If the player is online, that takes precedence
&& sender.isLoggedIn()) //Don't call the quit event if login failed
MCChatUtils.callLogoutEvent(sender, false); //The next line has to run *after* this one, so can't use the needsSync parameter
MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId());
sender.setLoggedIn(false);
});
}
} // ---- PermissionsEx warning is normal on logout ----
if (!start)
@ -65,8 +69,7 @@ public class MCChatPrivate {
for (val entry : MCChatUtils.ConnectedSenders.entrySet())
for (val valueEntry : entry.getValue().entrySet())
if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey(), valueEntry.getValue().getUser()) == null) //If the player is online then the fake player was already logged out
MCChatUtils.callLogoutEvent(valueEntry.getValue(), false); //This is sync
MCChatUtils.ConnectedSenders.clear();
MCChatUtils.callLogoutEvent(valueEntry.getValue(), !Bukkit.isPrimaryThread());
}
}

View file

@ -29,6 +29,7 @@ import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.AuthorNagException;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import javax.annotation.Nullable;
@ -37,6 +38,7 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.stream.Collectors;
@ -67,7 +69,7 @@ public class MCChatUtils {
}
private static boolean notEnabled() {
return getModule() == null;
return (module == null) || (!module.disabling && (getModule() == null)); //Allow using things while disabling the module
}
private static MinecraftChatModule getModule() {
@ -142,30 +144,34 @@ public class MCChatUtils {
return null;
}
public static void forAllMCChat(Consumer<Mono<MessageChannel>> action) {
if (notEnabled()) return;
action.accept(module.chatChannelMono());
public static Mono<?> forPublicPrivateChat(Function<Mono<MessageChannel>, Mono<?>> action) {
if (notEnabled()) return Mono.empty();
var list = new ArrayList<Mono<?>>();
list.add(action.apply(module.chatChannelMono()));
for (LastMsgData data : MCChatPrivate.lastmsgPerUser)
action.accept(Mono.just(data.channel));
list.add(action.apply(Mono.just(data.channel)));
// lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat
return Mono.whenDelayError(list);
}
/**
* For custom and all MC chat
*
* @param action The action to act
* @param action The action to act (cannot complete empty)
* @param toggle The toggle to check
* @param hookmsg Whether the message is also sent from the hook
*/
public static void forCustomAndAllMCChat(Consumer<Mono<MessageChannel>> action, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
if (notEnabled()) return;
public static Mono<?> forCustomAndAllMCChat(Function<Mono<MessageChannel>, Mono<?>> action, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
if (notEnabled()) return Mono.empty();
var list = new ArrayList<Publisher<?>>();
if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg)
forAllMCChat(action);
final Consumer<MCChatCustom.CustomLMD> customLMDConsumer = cc -> action.accept(Mono.just(cc.channel));
list.add(forPublicPrivateChat(action));
final Function<MCChatCustom.CustomLMD, Publisher<?>> customLMDFunction = cc -> action.apply(Mono.just(cc.channel));
if (toggle == null)
MCChatCustom.lastmsgCustom.forEach(customLMDConsumer);
MCChatCustom.lastmsgCustom.stream().map(customLMDFunction).forEach(list::add);
else
MCChatCustom.lastmsgCustom.stream().filter(cc -> (cc.toggles & toggle.flag) != 0).forEach(customLMDConsumer);
MCChatCustom.lastmsgCustom.stream().filter(cc -> (cc.toggles & toggle.flag) != 0).map(customLMDFunction).forEach(list::add);
return Mono.whenDelayError(list);
}
/**
@ -175,16 +181,17 @@ public class MCChatUtils {
* @param sender The sender to check perms of or null to send to all that has it toggled
* @param toggle The toggle to check or null to send to all allowed
*/
public static void forAllowedCustomMCChat(Consumer<Mono<MessageChannel>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle) {
if (notEnabled()) return;
MCChatCustom.lastmsgCustom.stream().filter(clmd -> {
public static Mono<?> forAllowedCustomMCChat(Function<Mono<MessageChannel>, Mono<?>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle) {
if (notEnabled()) return Mono.empty();
Stream<Publisher<?>> st = MCChatCustom.lastmsgCustom.stream().filter(clmd -> {
//new TBMCChannelConnectFakeEvent(sender, clmd.mcchannel).shouldSendTo(clmd.dcp) - Thought it was this simple hehe - Wait, it *should* be this simple
if (toggle != null && (clmd.toggles & toggle.flag) == 0)
return false; //If null then allow
if (sender == null)
return true;
return clmd.groupID.equals(clmd.mcchannel.getGroupID(sender));
}).forEach(cc -> action.accept(Mono.just(cc.channel))); //TODO: Send error messages on channel connect
}).map(cc -> action.apply(Mono.just(cc.channel))); //TODO: Send error messages on channel connect
return Mono.whenDelayError(st::iterator); //Can't convert as an iterator or inside the stream, but I can convert it as a stream
}
/**
@ -195,32 +202,35 @@ public class MCChatUtils {
* @param toggle The toggle to check or null to send to all allowed
* @param hookmsg Whether the message is also sent from the hook
*/
public static void forAllowedCustomAndAllMCChat(Consumer<Mono<MessageChannel>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
if (notEnabled()) return;
public static Mono<?> forAllowedCustomAndAllMCChat(Function<Mono<MessageChannel>, Mono<?>> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
if (notEnabled()) return Mono.empty();
var cc = forAllowedCustomMCChat(action, sender, toggle);
if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg)
forAllMCChat(action);
forAllowedCustomMCChat(action, sender, toggle);
return Mono.whenDelayError(forPublicPrivateChat(action), cc);
return Mono.whenDelayError(cc);
}
public static Consumer<Mono<MessageChannel>> send(String message) {
public static Function<Mono<MessageChannel>, Mono<?>> send(String message) {
return ch -> ch.flatMap(mc -> {
resetLastMessage(mc);
return mc.createMessage(DPUtils.sanitizeString(message));
}).subscribe();
});
}
public static void forAllowedMCChat(Consumer<Mono<MessageChannel>> action, TBMCSystemChatEvent event) {
if (notEnabled()) return;
public static Mono<?> forAllowedMCChat(Function<Mono<MessageChannel>, Mono<?>> action, TBMCSystemChatEvent event) {
if (notEnabled()) return Mono.empty();
var list = new ArrayList<Mono<?>>();
if (event.getChannel().isGlobal())
action.accept(module.chatChannelMono());
list.add(action.apply(module.chatChannelMono()));
for (LastMsgData data : MCChatPrivate.lastmsgPerUser)
if (event.shouldSendTo(getSender(data.channel.getId(), data.user)))
action.accept(Mono.just(data.channel)); //TODO: Only store ID?
list.add(action.apply(Mono.just(data.channel))); //TODO: Only store ID?
MCChatCustom.lastmsgCustom.stream().filter(clmd -> {
if (!clmd.brtoggles.contains(event.getTarget()))
return false;
return event.shouldSendTo(clmd.dcp);
}).map(clmd -> Mono.just(clmd.channel)).forEach(action);
}).map(clmd -> action.apply(Mono.just(clmd.channel))).forEach(list::add);
return Mono.whenDelayError(list);
}
/**
@ -357,8 +367,11 @@ public class MCChatUtils {
}
callEventExcludingSome(new PlayerJoinEvent(dcp, ""));
dcp.setLoggedIn(true);
if (module != null)
if (module != null) {
if (module.serverWatcher != null)
module.serverWatcher.fakePlayers.add(dcp);
module.log(dcp.getName() + " (" + dcp.getUniqueId() + ") logged in from Discord");
}
});
}
@ -374,8 +387,11 @@ public class MCChatUtils {
if (needsSync) callEventSync(event);
else callEventExcludingSome(event);
dcp.setLoggedIn(false);
if (module != null)
if (module != null) {
module.log(dcp.getName() + " (" + dcp.getUniqueId() + ") logged out from Discord");
if (module.serverWatcher != null)
module.serverWatcher.fakePlayers.remove(dcp);
}
}
static void callEventSync(Event event) {

View file

@ -63,7 +63,7 @@ class MCListener implements Listener {
}
final String message = e.getJoinMessage();
if (message != null && message.trim().length() > 0)
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true);
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe();
ChromaBot.getInstance().updatePlayerList();
});
}
@ -80,7 +80,7 @@ class MCListener implements Listener {
ChromaBot.getInstance()::updatePlayerList, 5);
final String message = e.getQuitMessage();
if (message != null && message.trim().length() > 0)
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true);
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe();
}
@EventHandler(priority = EventPriority.HIGHEST)
@ -92,7 +92,7 @@ class MCListener implements Listener {
@EventHandler(priority = EventPriority.LOW)
public void onPlayerDeath(PlayerDeathEvent e) {
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(e.getDeathMessage()), e.getEntity(), ChannelconBroadcast.DEATH, true);
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(e.getDeathMessage()), e.getEntity(), ChannelconBroadcast.DEATH, true).subscribe();
}
@EventHandler
@ -102,7 +102,7 @@ class MCListener implements Listener {
return;
final String msg = base.getDisplayName()
+ " is " + (e.getValue() ? "now" : "no longer") + " AFK.";
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, false);
MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, false).subscribe();
}
private ConfigData<Mono<Role>> muteRole() {
@ -137,12 +137,12 @@ class MCListener implements Listener {
@EventHandler
public void onChatSystemMessage(TBMCSystemChatEvent event) {
MCChatUtils.forAllowedMCChat(MCChatUtils.send(event.getMessage()), event);
MCChatUtils.forAllowedMCChat(MCChatUtils.send(event.getMessage()), event).subscribe();
}
@EventHandler
public void onBroadcastMessage(BroadcastMessageEvent event) {
MCChatUtils.forCustomAndAllMCChat(MCChatUtils.send(event.getMessage()), ChannelconBroadcast.BROADCAST, false);
MCChatUtils.forCustomAndAllMCChat(MCChatUtils.send(event.getMessage()), ChannelconBroadcast.BROADCAST, false).subscribe();
}
@EventHandler
@ -151,8 +151,8 @@ class MCListener implements Listener {
: event.getSender().getName();
//Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO
DiscordPlugin.mainServer.getEmojis().filter(e -> "YEEHAW".equals(e.getName()))
.take(1).singleOrEmpty().map(Optional::of).defaultIfEmpty(Optional.empty()).subscribe(yeehaw ->
MCChatUtils.forAllMCChat(MCChatUtils.send(name + (yeehaw.map(guildEmoji -> " <:YEEHAW:" + guildEmoji.getId().asString() + ">s").orElse(" YEEHAWs")))));
.take(1).singleOrEmpty().map(Optional::of).defaultIfEmpty(Optional.empty()).flatMap(yeehaw ->
MCChatUtils.forPublicPrivateChat(MCChatUtils.send(name + (yeehaw.map(guildEmoji -> " <:YEEHAW:" + guildEmoji.getId().asString() + ">s").orElse(" YEEHAWs"))))).subscribe();
}
@EventHandler

View file

@ -1,12 +1,13 @@
package buttondevteam.discordplugin.mcchat;
import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.channel.Channel;
import buttondevteam.discordplugin.ChannelconBroadcast;
import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.playerfaker.ServerWatcher;
import buttondevteam.discordplugin.playerfaker.perm.LPInjector;
import buttondevteam.discordplugin.util.DPState;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.architecture.Component;
@ -15,10 +16,11 @@ import buttondevteam.lib.architecture.ReadOnlyConfigData;
import com.google.common.collect.Lists;
import discord4j.common.util.Snowflake;
import discord4j.core.object.entity.channel.MessageChannel;
import discord4j.rest.util.Color;
import lombok.Getter;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.entity.Player;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
@ -30,9 +32,11 @@ import java.util.stream.Collectors;
* Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM.
*/
public class MinecraftChatModule extends Component<DiscordPlugin> {
public static DPState state = DPState.RUNNING;
private @Getter MCChatListener listener;
private ServerWatcher serverWatcher;
ServerWatcher serverWatcher;
private LPInjector lpInjector;
boolean disabling = false;
/**
* A list of commands that can be used in public chats - Warning: Some plugins will treat players as OPs, always test before allowing a command!
@ -123,6 +127,11 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
*/
private final ConfigData<Boolean> addFakePlayersToBukkit = getConfig().getData("addFakePlayersToBukkit", true);
/**
* Set by the component to report crashes.
*/
private final ConfigData<Boolean> serverUp = getConfig().getData("serverUp", false);
@Override
protected void enable() {
if (DPUtils.disableIfConfigErrorRes(this, chatChannel(), chatChannelMono()))
@ -158,7 +167,7 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
try {
if (lpInjector == null)
lpInjector = new LPInjector(MainPlugin.Instance);
lpInjector = new LPInjector(DiscordPlugin.plugin);
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to init LuckPerms injector", e, this);
} catch (NoClassDefFoundError e) {
@ -170,23 +179,68 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
try {
serverWatcher = new ServerWatcher();
serverWatcher.enableDisable(true);
log("Finished hooking into the server");
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to hack the server (object)!", e, this);
}
}
if (state == DPState.RESTARTING_PLUGIN) { //These will only execute if the chat is enabled
sendStateMessage(Color.CYAN, "Discord plugin restarted - chat connected."); //Really important to note the chat, hmm
state = DPState.RUNNING;
} else if (state == DPState.DISABLED_MCCHAT) {
sendStateMessage(Color.CYAN, "Minecraft chat enabled - chat connected.");
state = DPState.RUNNING;
} else if (serverUp.get()) {
sendStateMessage(Color.YELLOW, "Server started after a crash - chat connected.");
val thr = new Throwable("The server shut down unexpectedly. See the log of the previous run for more details.");
thr.setStackTrace(new StackTraceElement[0]);
TBMCCoreAPI.SendException("The server crashed!", thr, this);
} else
sendStateMessage(Color.GREEN, "Server started - chat connected.");
serverUp.set(true);
}
@Override
protected void disable() {
disabling = true;
if (state == DPState.RESTARTING_PLUGIN) //These will only execute if the chat is enabled
sendStateMessage(Color.ORANGE, "Discord plugin restarting");
else if (state == DPState.RUNNING) {
sendStateMessage(Color.ORANGE, "Minecraft chat disabled");
state = DPState.DISABLED_MCCHAT;
} else {
String kickmsg = Bukkit.getOnlinePlayers().size() > 0
? (DPUtils
.sanitizeString(Bukkit.getOnlinePlayers().stream()
.map(Player::getDisplayName).collect(Collectors.joining(", ")))
+ (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ")
+ "thrown out") //TODO: Make configurable
: "";
if (state == DPState.RESTARTING_SERVER)
sendStateMessage(Color.ORANGE, "Server restarting", kickmsg);
else if (state == DPState.STOPPING_SERVER)
sendStateMessage(Color.RED, "Server stopping", kickmsg);
else
sendStateMessage(Color.GRAY, "Unknown state, please report.");
} //If 'restart' is disabled then this isn't shown even if joinleave is enabled
serverUp.set(false); //Disable even if just the component is disabled because that way it won't falsely report crashes
try { //If it's not enabled it won't do anything
if (serverWatcher != null)
if (serverWatcher != null) {
serverWatcher.enableDisable(false);
} catch (Exception e) {
log("Finished unhooking the server");
}
} catch (
Exception e) {
TBMCCoreAPI.SendException("Failed to restore the server object!", e, this);
}
val chcons = MCChatCustom.getCustomChats();
val chconsc = getConfig().getConfig().createSection("chcons");
for (val chcon : chcons) {
for (
val chcon : chcons) {
val chconc = chconsc.createSection(chcon.channel.getId().asString());
chconc.set("mcchid", chcon.mcchannel.ID);
chconc.set("chid", chcon.channel.getId().asLong());
@ -197,11 +251,23 @@ public class MinecraftChatModule extends Component<DiscordPlugin> {
chconc.set("toggles", chcon.toggles);
chconc.set("brtoggles", chcon.brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::getName).collect(Collectors.toList()));
}
MCChatListener.stop(true);
listener.stop(true);
disabling = false;
}
@Override
protected void unregister(JavaPlugin plugin) {
lpInjector = null; //Plugin restart, events need to be registered again
/**
* It will block to make sure all messages are sent
*/
private void sendStateMessage(Color color, String message) {
MCChatUtils.forCustomAndAllMCChat(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(color)
.setTitle(message))), ChannelconBroadcast.RESTART, false).block();
}
/**
* It will block to make sure all messages are sent
*/
private void sendStateMessage(Color color, String message, String extra) {
MCChatUtils.forCustomAndAllMCChat(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(color)
.setTitle(message).setDescription(extra))), ChannelconBroadcast.RESTART, false).block();
}
}

View file

@ -7,6 +7,8 @@ import buttondevteam.discordplugin.DiscordSenderBase;
import buttondevteam.discordplugin.commands.ConnectCommand;
import buttondevteam.discordplugin.commands.VersionCommand;
import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import buttondevteam.discordplugin.util.DPState;
import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.ICommand2MC;
@ -58,7 +60,7 @@ public class DiscordMCCommand extends ICommand2MC {
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = {
"Reload Discord plugin",
"Reloads the config. To apply some changes, you may need to also run /discord reset."
"Reloads the config. To apply some changes, you may need to also run /discord restart."
})
public void reload(CommandSender sender) {
if (DiscordPlugin.plugin.tryReloadConfig())
@ -67,26 +69,24 @@ public class DiscordMCCommand extends ICommand2MC {
sender.sendMessage("§cFailed to reload config.");
}
public static boolean resetting = false;
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = {
"Reset ChromaBot", //
"Restart the plugin", //
"This command disables and then enables the plugin." //
})
public void reset(CommandSender sender) {
public void restart(CommandSender sender) {
Runnable task = () -> {
if (!DiscordPlugin.plugin.tryReloadConfig()) {
sender.sendMessage("§cFailed to reload config so not resetting. Check the console.");
sender.sendMessage("§cFailed to reload config so not restarting. Check the console.");
return;
}
resetting = true; //Turned off after sending enable message (ReadyEvent)
MinecraftChatModule.state = DPState.RESTARTING_PLUGIN; //Reset in MinecraftChatModule
sender.sendMessage("§bDisabling DiscordPlugin...");
Bukkit.getPluginManager().disablePlugin(DiscordPlugin.plugin);
if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors
sender.sendMessage("§bEnabling DiscordPlugin...");
Bukkit.getPluginManager().enablePlugin(DiscordPlugin.plugin);
if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors
sender.sendMessage("§bReset finished!");
sender.sendMessage("§bRestart finished!");
};
if (!Bukkit.getName().equals("Paper")) {
getPlugin().getLogger().warning("Async plugin events are not supported by the server, running on main thread");
@ -116,9 +116,8 @@ public class DiscordMCCommand extends ICommand2MC {
}
DiscordPlugin.mainServer.getInvites().limitRequest(1)
.switchIfEmpty(Mono.fromRunnable(() -> sender.sendMessage("§cNo invites found for the server.")))
.subscribe(inv -> {
sender.sendMessage("§bInvite link: https://discord.gg/" + inv.getCode());
}, e -> sender.sendMessage("§cThe invite link is not set and the bot has no permission to get it."));
.subscribe(inv -> sender.sendMessage("§bInvite link: https://discord.gg/" + inv.getCode()),
e -> sender.sendMessage("§cThe invite link is not set and the bot has no permission to get it."));
}
@Override

View file

@ -15,7 +15,7 @@ import java.util.*;
public class ServerWatcher {
private List<Player> playerList;
private final List<Player> fakePlayers = new ArrayList<>();
public final List<Player> fakePlayers = new ArrayList<>();
private Server origServer;
@IgnoreForBinding
@ -43,12 +43,12 @@ public class ServerWatcher {
.filter(dcp -> dcp.getName().equalsIgnoreCase(argument)).findAny().orElse(null);
}
break;
case "getOnlinePlayers":
/*case "getOnlinePlayers":
if (playerList == null) {
@SuppressWarnings("unchecked") var list = (List<Player>) method.invoke(origServer, invocation.getArguments());
playerList = new AppendListView<>(list, fakePlayers);
}
return playerList;
} - Your scientists were so preoccupied with whether or not they could, they didnt stop to think if they should.
return playerList;*/
case "createProfile": //Paper's method, casts the player to a CraftPlayer
if (pc == 2) {
UUID uuid = invocation.getArgument(0);

View file

@ -1,7 +1,7 @@
package buttondevteam.discordplugin.playerfaker.perm;
import buttondevteam.core.MainPlugin;
import buttondevteam.discordplugin.DiscordConnectedPlayer;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.lib.TBMCCoreAPI;
import me.lucko.luckperms.bukkit.LPBukkitBootstrap;
@ -42,7 +42,7 @@ public final class LPInjector implements Listener { //Disable login event for Lu
private final Method setOldPermissible;
private final Method getOldPermissible;
public LPInjector(MainPlugin mp) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
public LPInjector(DiscordPlugin dp) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
LPBukkitBootstrap bs = (LPBukkitBootstrap) Bukkit.getPluginManager().getPlugin("LuckPerms");
Field field = LPBukkitBootstrap.class.getDeclaredField("plugin");
field.setAccessible(true);
@ -77,7 +77,7 @@ public final class LPInjector implements Listener { //Disable login event for Lu
getOldPermissible = LuckPermsPermissible.class.getDeclaredMethod("getOldPermissible");
getOldPermissible.setAccessible(true);
TBMCCoreAPI.RegisterEventsForExceptions(this, mp);
TBMCCoreAPI.RegisterEventsForExceptions(this, dp);
}
@ -146,7 +146,7 @@ public final class LPInjector implements Listener { //Disable login event for Lu
t.printStackTrace();
e.disallow(PlayerLoginEvent.Result.KICK_OTHER, Message.LOADING_SETUP_ERROR.asString(plugin.getLocaleManager()));
return;
//return;
}
//this.plugin.getContextManager().signalContextUpdate(player);
@ -167,7 +167,7 @@ public final class LPInjector implements Listener { //Disable login event for Lu
this.plugin.getBootstrap().getServer().getScheduler().runTaskLaterAsynchronously(this.plugin.getBootstrap(), () -> {
// Remove the custom permissible
try {
uninject(player, true);
uninject(player);
} catch (Exception ex) {
ex.printStackTrace();
}
@ -207,7 +207,7 @@ public final class LPInjector implements Listener { //Disable login event for Lu
player.setPerm(newPermissible);
}
private void uninject(DiscordConnectedPlayer player, boolean dummy) throws Exception {
private void uninject(DiscordConnectedPlayer player) throws Exception {
// gets the players current permissible.
PermissibleBase permissible = player.getPerm();
@ -223,18 +223,9 @@ public final class LPInjector implements Listener { //Disable login event for Lu
((AtomicBoolean) getActive.invoke(lpPermissible)).set(false);
// handle the replacement permissible.
if (dummy) {
// just inject a dummy class. this is used when we know the player is about to quit the server.
player.setPerm(DummyPermissibleBase.INSTANCE);
// just inject a dummy class. this is used when we know the player is about to quit the server.
player.setPerm(DummyPermissibleBase.INSTANCE);
} else {
PermissibleBase newPb = (PermissibleBase) getOldPermissible.invoke(lpPermissible);
if (newPb == null) {
newPb = new PermissibleBase(player);
}
player.setPerm(newPb);
}
}
}
}

View file

@ -0,0 +1,24 @@
package buttondevteam.discordplugin.util;
public enum DPState {
/**
* Used from server start until anything else happens
*/
RUNNING,
/**
* Used when /restart is detected
*/
RESTARTING_SERVER,
/**
* Used when the plugin is disabled by outside forces
*/
STOPPING_SERVER,
/**
* Used when /discord restart is run
*/
RESTARTING_PLUGIN,
/**
* Used when the plugin is in the RUNNING state when the chat is disabled
*/
DISABLED_MCCHAT
}