Run Spotless on src/main

This commit is contained in:
KingRainbow44
2023-03-31 22:30:45 -04:00
parent 99822b0e22
commit fc05602128
1003 changed files with 60650 additions and 58050 deletions

View File

@@ -1,135 +1,124 @@
package emu.grasscutter.plugin;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.plugin.api.ServerHook;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.utils.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.InputStream;
import java.net.URLClassLoader;
/**
* The base class for all plugins to extend.
*/
public abstract class Plugin {
private final ServerHook server = ServerHook.getInstance();
private PluginIdentifier identifier;
private URLClassLoader classLoader;
private File dataFolder;
private Logger logger;
/**
* This method is reflected into.
* <p>
* Set plugin variables.
*
* @param identifier The plugin's identifier.
*/
private void initializePlugin(PluginIdentifier identifier, URLClassLoader classLoader) {
if (this.identifier != null) {
Grasscutter.getLogger().warn(this.identifier.name + " had a reinitialization attempt.");
return;
}
this.identifier = identifier;
this.classLoader = classLoader;
this.dataFolder = FileUtils.getPluginPath(identifier.name).toFile();
this.logger = LoggerFactory.getLogger(identifier.name);
if (!this.dataFolder.exists() && !this.dataFolder.mkdirs()) {
Grasscutter.getLogger().warn("Failed to create plugin data folder for " + this.identifier.name);
}
}
/**
* The plugin's identifier instance.
*
* @return An instance of {@link PluginIdentifier}.
*/
public final PluginIdentifier getIdentifier() {
return this.identifier;
}
/**
* Get the plugin's name.
*/
public final String getName() {
return this.identifier.name;
}
/**
* Get the plugin's description.
*/
public final String getDescription() {
return this.identifier.description;
}
/**
* Get the plugin's version.
*/
public final String getVersion() {
return this.identifier.version;
}
/**
* Returns the server that initialized the plugin.
*
* @return A server instance.
*/
public final GameServer getServer() {
return this.server.getGameServer();
}
/**
* Returns an input stream for a resource in the JAR file.
*
* @param resourceName The name of the resource.
* @return An input stream.
*/
public final InputStream getResource(String resourceName) {
return this.classLoader.getResourceAsStream(resourceName);
}
/**
* Returns a directory where plugins can store data files.
*
* @return A directory on the file system.
*/
public final File getDataFolder() {
return this.dataFolder;
}
/**
* Returns the server hook.
*
* @return A server hook singleton.
*/
public final ServerHook getHandle() {
return this.server;
}
/**
* Returns the plugin's logger.
*
* @return A SLF4J logger.
*/
public final Logger getLogger() {
return this.logger;
}
/* Called when the plugin is first loaded. */
public void onLoad() {
}
/* Called after (most of) the server enables. */
public void onEnable() {
}
/* Called before the server disables. */
public void onDisable() {
}
}
package emu.grasscutter.plugin;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.plugin.api.ServerHook;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.utils.FileUtils;
import java.io.File;
import java.io.InputStream;
import java.net.URLClassLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** The base class for all plugins to extend. */
public abstract class Plugin {
private final ServerHook server = ServerHook.getInstance();
private PluginIdentifier identifier;
private URLClassLoader classLoader;
private File dataFolder;
private Logger logger;
/**
* This method is reflected into.
*
* <p>Set plugin variables.
*
* @param identifier The plugin's identifier.
*/
private void initializePlugin(PluginIdentifier identifier, URLClassLoader classLoader) {
if (this.identifier != null) {
Grasscutter.getLogger().warn(this.identifier.name + " had a reinitialization attempt.");
return;
}
this.identifier = identifier;
this.classLoader = classLoader;
this.dataFolder = FileUtils.getPluginPath(identifier.name).toFile();
this.logger = LoggerFactory.getLogger(identifier.name);
if (!this.dataFolder.exists() && !this.dataFolder.mkdirs()) {
Grasscutter.getLogger()
.warn("Failed to create plugin data folder for " + this.identifier.name);
}
}
/**
* The plugin's identifier instance.
*
* @return An instance of {@link PluginIdentifier}.
*/
public final PluginIdentifier getIdentifier() {
return this.identifier;
}
/** Get the plugin's name. */
public final String getName() {
return this.identifier.name;
}
/** Get the plugin's description. */
public final String getDescription() {
return this.identifier.description;
}
/** Get the plugin's version. */
public final String getVersion() {
return this.identifier.version;
}
/**
* Returns the server that initialized the plugin.
*
* @return A server instance.
*/
public final GameServer getServer() {
return this.server.getGameServer();
}
/**
* Returns an input stream for a resource in the JAR file.
*
* @param resourceName The name of the resource.
* @return An input stream.
*/
public final InputStream getResource(String resourceName) {
return this.classLoader.getResourceAsStream(resourceName);
}
/**
* Returns a directory where plugins can store data files.
*
* @return A directory on the file system.
*/
public final File getDataFolder() {
return this.dataFolder;
}
/**
* Returns the server hook.
*
* @return A server hook singleton.
*/
public final ServerHook getHandle() {
return this.server;
}
/**
* Returns the plugin's logger.
*
* @return A SLF4J logger.
*/
public final Logger getLogger() {
return this.logger;
}
/* Called when the plugin is first loaded. */
public void onLoad() {}
/* Called after (most of) the server enables. */
public void onEnable() {}
/* Called before the server disables. */
public void onDisable() {}
}

View File

@@ -1,21 +1,19 @@
package emu.grasscutter.plugin;
/**
* The data contained in the plugin's `plugin.json` file.
*/
public final class PluginConfig {
public String name, description, version;
public String mainClass;
public String[] authors;
public String[] loadAfter;
/**
* Attempts to validate this config instance.
*
* @return True if the config is valid, false otherwise.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean validate() {
return name != null && description != null && mainClass != null;
}
}
package emu.grasscutter.plugin;
/** The data contained in the plugin's `plugin.json` file. */
public final class PluginConfig {
public String name, description, version;
public String mainClass;
public String[] authors;
public String[] loadAfter;
/**
* Attempts to validate this config instance.
*
* @return True if the config is valid, false otherwise.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean validate() {
return name != null && description != null && mainClass != null;
}
}

View File

@@ -1,29 +1,22 @@
package emu.grasscutter.plugin;
// TODO: Potentially replace with Lombok?
public final class PluginIdentifier {
public final String name, description, version;
public final String[] authors;
public PluginIdentifier(
String name, String description, String version,
String[] authors
) {
this.name = name;
this.description = description;
this.version = version;
this.authors = authors;
}
/**
* Converts a {@link PluginConfig} into a {@link PluginIdentifier}.
*/
public static PluginIdentifier fromPluginConfig(PluginConfig config) {
if (!config.validate())
throw new IllegalArgumentException("A valid plugin config is required to convert into a plugin identifier.");
return new PluginIdentifier(
config.name, config.description, config.version,
config.authors
);
}
}
package emu.grasscutter.plugin;
// TODO: Potentially replace with Lombok?
public final class PluginIdentifier {
public final String name, description, version;
public final String[] authors;
public PluginIdentifier(String name, String description, String version, String[] authors) {
this.name = name;
this.description = description;
this.version = version;
this.authors = authors;
}
/** Converts a {@link PluginConfig} into a {@link PluginIdentifier}. */
public static PluginIdentifier fromPluginConfig(PluginConfig config) {
if (!config.validate())
throw new IllegalArgumentException(
"A valid plugin config is required to convert into a plugin identifier.");
return new PluginIdentifier(config.name, config.description, config.version, config.authors);
}
}

View File

@@ -1,319 +1,321 @@
package emu.grasscutter.plugin;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.event.Event;
import emu.grasscutter.server.event.EventHandler;
import emu.grasscutter.server.event.HandlerPriority;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.JsonUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import javax.annotation.Nullable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import static emu.grasscutter.utils.Language.translate;
/**
* Manages the server's plugins and the event system.
*/
public final class PluginManager {
/* All loaded plugins. */
private final Map<String, Plugin> plugins = new LinkedHashMap<>();
/* All currently registered listeners per plugin. */
private final Map<Plugin, List<EventHandler<? extends Event>>> listeners = new LinkedHashMap<>();
public PluginManager() {
this.loadPlugins(); // Load all plugins from the plugins directory.
}
/**
* Loads plugins from the config-specified directory.
*/
private void loadPlugins() {
File pluginsDir = FileUtils.getPluginPath("").toFile();
if (!pluginsDir.exists() && !pluginsDir.mkdirs()) {
Grasscutter.getLogger().error(translate("plugin.directory_failed", pluginsDir.getAbsolutePath()));
return;
}
File[] files = pluginsDir.listFiles();
if (files == null) {
// The directory is empty, there aren't any plugins to load.
return;
}
List<File> plugins = Arrays.stream(files)
.filter(file -> file.getName().endsWith(".jar"))
.toList();
URL[] pluginNames = new URL[plugins.size()];
plugins.forEach(plugin -> {
try {
pluginNames[plugins.indexOf(plugin)] = plugin.toURI().toURL();
} catch (MalformedURLException exception) {
Grasscutter.getLogger().warn(translate("plugin.unable_to_load"), exception);
}
});
// Create a class loader for the plugins.
URLClassLoader classLoader = new URLClassLoader(pluginNames);
// Create a list of plugins that require dependencies.
List<PluginData> dependencies = new ArrayList<>();
// Initialize all plugins.
for (var plugin : plugins) {
try {
URL url = plugin.toURI().toURL();
try (URLClassLoader loader = new URLClassLoader(new URL[]{url})) {
// Find the plugin.json file for each plugin.
URL configFile = loader.findResource("plugin.json");
// Open the config file for reading.
InputStreamReader fileReader = new InputStreamReader(configFile.openStream());
// Create a plugin config instance from the config file.
PluginConfig pluginConfig = JsonUtils.loadToClass(fileReader, PluginConfig.class);
// Check if the plugin config is valid.
if (!pluginConfig.validate()) {
Grasscutter.getLogger().warn(translate("plugin.invalid_config", plugin.getName()));
return;
}
// Create a JAR file instance from the plugin's URL.
JarFile jarFile = new JarFile(plugin);
// Load all class files from the JAR file.
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.isDirectory() || !entry.getName().endsWith(".class") || entry.getName().contains("module-info"))
continue;
String className = entry.getName().replace(".class", "").replace("/", ".");
classLoader.loadClass(className); // Use the same class loader for ALL plugins.
}
// Create a plugin instance.
Class<?> pluginClass = classLoader.loadClass(pluginConfig.mainClass);
Plugin pluginInstance = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
// Close the file reader.
fileReader.close();
// Check if the plugin has alternate dependencies.
if (pluginConfig.loadAfter != null && pluginConfig.loadAfter.length > 0) {
// Add the plugin to a "load later" list.
dependencies.add(new PluginData(
pluginInstance, PluginIdentifier.fromPluginConfig(pluginConfig),
loader, pluginConfig.loadAfter));
continue;
}
// Load the plugin.
this.loadPlugin(pluginInstance, PluginIdentifier.fromPluginConfig(pluginConfig), loader);
} catch (ClassNotFoundException ignored) {
Grasscutter.getLogger().warn(translate("plugin.invalid_main_class", plugin.getName()));
} catch (FileNotFoundException ignored) {
Grasscutter.getLogger().warn(translate("plugin.missing_config", plugin.getName()));
}
} catch (Exception exception) {
Grasscutter.getLogger().error(translate("plugin.failed_to_load_plugin", plugin.getName()), exception);
}
}
// Load plugins with dependencies.
int depth = 0;
final int maxDepth = 30;
while (!dependencies.isEmpty()) {
// Check if the depth is too high.
if (depth >= maxDepth) {
Grasscutter.getLogger().error(translate("plugin.failed_to_load_dependencies"));
break;
}
try {
// Get the next plugin to load.
var pluginData = dependencies.get(0);
// Check if the plugin's dependencies are loaded.
if (!this.plugins.keySet().containsAll(List.of(pluginData.getDependencies()))) {
depth++; // Increase depth counter.
continue; // Continue to next plugin.
}
// Remove the plugin from the list of dependencies.
dependencies.remove(pluginData);
// Load the plugin.
this.loadPlugin(pluginData.getPlugin(), pluginData.getIdentifier(), pluginData.getClassLoader());
} catch (Exception exception) {
Grasscutter.getLogger().error(translate("plugin.failed_to_load"), exception);
depth++;
}
}
}
/**
* Load the specified plugin.
*
* @param plugin The plugin instance.
*/
private void loadPlugin(Plugin plugin, PluginIdentifier identifier, URLClassLoader classLoader) {
Grasscutter.getLogger().info(translate("plugin.loading_plugin", identifier.name));
// Add the plugin's identifier.
try {
Class<Plugin> pluginClass = Plugin.class;
Method method = pluginClass.getDeclaredMethod("initializePlugin", PluginIdentifier.class, URLClassLoader.class);
method.setAccessible(true);
method.invoke(plugin, identifier, classLoader);
method.setAccessible(false);
} catch (Exception ignored) {
Grasscutter.getLogger().warn(translate("plugin.failed_add_id", identifier.name));
}
// Add the plugin to the list of loaded plugins.
this.plugins.put(identifier.name, plugin);
// Create a collection for the plugin's listeners.
this.listeners.put(plugin, new ArrayList<>());
// Call the plugin's onLoad method.
try {
plugin.onLoad();
} catch (Throwable exception) {
Grasscutter.getLogger().error(translate("plugin.failed_to_load_plugin", identifier.name), exception);
}
}
/**
* Enables all registered plugins.
*/
public void enablePlugins() {
this.plugins.forEach((name, plugin) -> {
Grasscutter.getLogger().info(translate("plugin.enabling_plugin", name));
try {
plugin.onEnable();
} catch (Throwable exception) {
Grasscutter.getLogger().error(translate("plugin.enabling_failed", name), exception);
}
});
}
/**
* Disables all registered plugins.
*/
public void disablePlugins() {
this.plugins.forEach((name, plugin) -> {
Grasscutter.getLogger().info(translate("plugin.disabling_plugin", name));
this.disablePlugin(plugin);
});
}
/**
* Registers a plugin's event listener.
*
* @param plugin The plugin registering the listener.
* @param listener The event listener.
*/
public void registerListener(Plugin plugin, EventHandler<? extends Event> listener) {
this.listeners.get(plugin).add(listener);
}
/**
* Invoke the provided event on all registered event listeners.
*
* @param event The event to invoke.
*/
public void invokeEvent(Event event) {
EnumSet.allOf(HandlerPriority.class)
.forEach(priority -> this.checkAndFilter(event, priority));
}
/**
* Check an event to handlers for the priority.
*
* @param event The event being called.
* @param priority The priority to call for.
*/
private void checkAndFilter(Event event, HandlerPriority priority) {
// Add all listeners from every plugin.
this.listeners.values().stream()
.flatMap(Collection::stream)
// Filter the listeners by priority.
.filter(handler -> handler.handles().isInstance(event))
.filter(handler -> handler.getPriority() == priority)
// Invoke the event.
.forEach(handler -> this.invokeHandler(event, handler));
}
/**
* Gets a plugin's instance by its name.
*
* @param name The name of the plugin.
* @return Either null, or the plugin's instance.
*/
@Nullable
public Plugin getPlugin(String name) {
return this.plugins.get(name);
}
/**
* Enables a plugin.
*
* @param plugin The plugin to enable.
*/
public void enablePlugin(Plugin plugin) {
try {
// Call the plugin's onEnable method.
plugin.onEnable();
} catch (Exception exception) {
Grasscutter.getLogger().error(translate("plugin.enabling_failed", plugin.getName()), exception);
}
}
/**
* Disables a plugin.
*
* @param plugin The plugin to disable.
*/
public void disablePlugin(Plugin plugin) {
try {
// Call the plugin's onDisable method.
plugin.onDisable();
} catch (Exception exception) {
Grasscutter.getLogger().error(translate("plugin.disabling_failed", plugin.getName()), exception);
}
// Un-register all listeners.
this.listeners.remove(plugin);
}
/**
* Performs logic checks then invokes the provided event handler.
*
* @param event The event passed through to the handler.
* @param handler The handler to invoke.
*/
@SuppressWarnings("unchecked")
private <T extends Event> void invokeHandler(Event event, EventHandler<T> handler) {
if (!event.isCanceled() ||
(event.isCanceled() && handler.ignoresCanceled())
) handler.getCallback().consume((T) event);
}
/* Data about an unloaded plugin. */
@AllArgsConstructor
@Getter
static class PluginData {
private Plugin plugin;
private PluginIdentifier identifier;
private URLClassLoader classLoader;
private String[] dependencies;
}
}
package emu.grasscutter.plugin;
import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.event.Event;
import emu.grasscutter.server.event.EventHandler;
import emu.grasscutter.server.event.HandlerPriority;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.JsonUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Getter;
/** Manages the server's plugins and the event system. */
public final class PluginManager {
/* All loaded plugins. */
private final Map<String, Plugin> plugins = new LinkedHashMap<>();
/* All currently registered listeners per plugin. */
private final Map<Plugin, List<EventHandler<? extends Event>>> listeners = new LinkedHashMap<>();
public PluginManager() {
this.loadPlugins(); // Load all plugins from the plugins directory.
}
/** Loads plugins from the config-specified directory. */
private void loadPlugins() {
File pluginsDir = FileUtils.getPluginPath("").toFile();
if (!pluginsDir.exists() && !pluginsDir.mkdirs()) {
Grasscutter.getLogger()
.error(translate("plugin.directory_failed", pluginsDir.getAbsolutePath()));
return;
}
File[] files = pluginsDir.listFiles();
if (files == null) {
// The directory is empty, there aren't any plugins to load.
return;
}
List<File> plugins =
Arrays.stream(files).filter(file -> file.getName().endsWith(".jar")).toList();
URL[] pluginNames = new URL[plugins.size()];
plugins.forEach(
plugin -> {
try {
pluginNames[plugins.indexOf(plugin)] = plugin.toURI().toURL();
} catch (MalformedURLException exception) {
Grasscutter.getLogger().warn(translate("plugin.unable_to_load"), exception);
}
});
// Create a class loader for the plugins.
URLClassLoader classLoader = new URLClassLoader(pluginNames);
// Create a list of plugins that require dependencies.
List<PluginData> dependencies = new ArrayList<>();
// Initialize all plugins.
for (var plugin : plugins) {
try {
URL url = plugin.toURI().toURL();
try (URLClassLoader loader = new URLClassLoader(new URL[] {url})) {
// Find the plugin.json file for each plugin.
URL configFile = loader.findResource("plugin.json");
// Open the config file for reading.
InputStreamReader fileReader = new InputStreamReader(configFile.openStream());
// Create a plugin config instance from the config file.
PluginConfig pluginConfig = JsonUtils.loadToClass(fileReader, PluginConfig.class);
// Check if the plugin config is valid.
if (!pluginConfig.validate()) {
Grasscutter.getLogger().warn(translate("plugin.invalid_config", plugin.getName()));
return;
}
// Create a JAR file instance from the plugin's URL.
JarFile jarFile = new JarFile(plugin);
// Load all class files from the JAR file.
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.isDirectory()
|| !entry.getName().endsWith(".class")
|| entry.getName().contains("module-info")) continue;
String className = entry.getName().replace(".class", "").replace("/", ".");
classLoader.loadClass(className); // Use the same class loader for ALL plugins.
}
// Create a plugin instance.
Class<?> pluginClass = classLoader.loadClass(pluginConfig.mainClass);
Plugin pluginInstance = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
// Close the file reader.
fileReader.close();
// Check if the plugin has alternate dependencies.
if (pluginConfig.loadAfter != null && pluginConfig.loadAfter.length > 0) {
// Add the plugin to a "load later" list.
dependencies.add(
new PluginData(
pluginInstance,
PluginIdentifier.fromPluginConfig(pluginConfig),
loader,
pluginConfig.loadAfter));
continue;
}
// Load the plugin.
this.loadPlugin(pluginInstance, PluginIdentifier.fromPluginConfig(pluginConfig), loader);
} catch (ClassNotFoundException ignored) {
Grasscutter.getLogger().warn(translate("plugin.invalid_main_class", plugin.getName()));
} catch (FileNotFoundException ignored) {
Grasscutter.getLogger().warn(translate("plugin.missing_config", plugin.getName()));
}
} catch (Exception exception) {
Grasscutter.getLogger()
.error(translate("plugin.failed_to_load_plugin", plugin.getName()), exception);
}
}
// Load plugins with dependencies.
int depth = 0;
final int maxDepth = 30;
while (!dependencies.isEmpty()) {
// Check if the depth is too high.
if (depth >= maxDepth) {
Grasscutter.getLogger().error(translate("plugin.failed_to_load_dependencies"));
break;
}
try {
// Get the next plugin to load.
var pluginData = dependencies.get(0);
// Check if the plugin's dependencies are loaded.
if (!this.plugins.keySet().containsAll(List.of(pluginData.getDependencies()))) {
depth++; // Increase depth counter.
continue; // Continue to next plugin.
}
// Remove the plugin from the list of dependencies.
dependencies.remove(pluginData);
// Load the plugin.
this.loadPlugin(
pluginData.getPlugin(), pluginData.getIdentifier(), pluginData.getClassLoader());
} catch (Exception exception) {
Grasscutter.getLogger().error(translate("plugin.failed_to_load"), exception);
depth++;
}
}
}
/**
* Load the specified plugin.
*
* @param plugin The plugin instance.
*/
private void loadPlugin(Plugin plugin, PluginIdentifier identifier, URLClassLoader classLoader) {
Grasscutter.getLogger().info(translate("plugin.loading_plugin", identifier.name));
// Add the plugin's identifier.
try {
Class<Plugin> pluginClass = Plugin.class;
Method method =
pluginClass.getDeclaredMethod(
"initializePlugin", PluginIdentifier.class, URLClassLoader.class);
method.setAccessible(true);
method.invoke(plugin, identifier, classLoader);
method.setAccessible(false);
} catch (Exception ignored) {
Grasscutter.getLogger().warn(translate("plugin.failed_add_id", identifier.name));
}
// Add the plugin to the list of loaded plugins.
this.plugins.put(identifier.name, plugin);
// Create a collection for the plugin's listeners.
this.listeners.put(plugin, new ArrayList<>());
// Call the plugin's onLoad method.
try {
plugin.onLoad();
} catch (Throwable exception) {
Grasscutter.getLogger()
.error(translate("plugin.failed_to_load_plugin", identifier.name), exception);
}
}
/** Enables all registered plugins. */
public void enablePlugins() {
this.plugins.forEach(
(name, plugin) -> {
Grasscutter.getLogger().info(translate("plugin.enabling_plugin", name));
try {
plugin.onEnable();
} catch (Throwable exception) {
Grasscutter.getLogger().error(translate("plugin.enabling_failed", name), exception);
}
});
}
/** Disables all registered plugins. */
public void disablePlugins() {
this.plugins.forEach(
(name, plugin) -> {
Grasscutter.getLogger().info(translate("plugin.disabling_plugin", name));
this.disablePlugin(plugin);
});
}
/**
* Registers a plugin's event listener.
*
* @param plugin The plugin registering the listener.
* @param listener The event listener.
*/
public void registerListener(Plugin plugin, EventHandler<? extends Event> listener) {
this.listeners.get(plugin).add(listener);
}
/**
* Invoke the provided event on all registered event listeners.
*
* @param event The event to invoke.
*/
public void invokeEvent(Event event) {
EnumSet.allOf(HandlerPriority.class).forEach(priority -> this.checkAndFilter(event, priority));
}
/**
* Check an event to handlers for the priority.
*
* @param event The event being called.
* @param priority The priority to call for.
*/
private void checkAndFilter(Event event, HandlerPriority priority) {
// Add all listeners from every plugin.
this.listeners.values().stream()
.flatMap(Collection::stream)
// Filter the listeners by priority.
.filter(handler -> handler.handles().isInstance(event))
.filter(handler -> handler.getPriority() == priority)
// Invoke the event.
.forEach(handler -> this.invokeHandler(event, handler));
}
/**
* Gets a plugin's instance by its name.
*
* @param name The name of the plugin.
* @return Either null, or the plugin's instance.
*/
@Nullable public Plugin getPlugin(String name) {
return this.plugins.get(name);
}
/**
* Enables a plugin.
*
* @param plugin The plugin to enable.
*/
public void enablePlugin(Plugin plugin) {
try {
// Call the plugin's onEnable method.
plugin.onEnable();
} catch (Exception exception) {
Grasscutter.getLogger()
.error(translate("plugin.enabling_failed", plugin.getName()), exception);
}
}
/**
* Disables a plugin.
*
* @param plugin The plugin to disable.
*/
public void disablePlugin(Plugin plugin) {
try {
// Call the plugin's onDisable method.
plugin.onDisable();
} catch (Exception exception) {
Grasscutter.getLogger()
.error(translate("plugin.disabling_failed", plugin.getName()), exception);
}
// Un-register all listeners.
this.listeners.remove(plugin);
}
/**
* Performs logic checks then invokes the provided event handler.
*
* @param event The event passed through to the handler.
* @param handler The handler to invoke.
*/
@SuppressWarnings("unchecked")
private <T extends Event> void invokeHandler(Event event, EventHandler<T> handler) {
if (!event.isCanceled() || (event.isCanceled() && handler.ignoresCanceled()))
handler.getCallback().consume((T) event);
}
/* Data about an unloaded plugin. */
@AllArgsConstructor
@Getter
static class PluginData {
private Plugin plugin;
private PluginIdentifier identifier;
private URLClassLoader classLoader;
private String[] dependencies;
}
}

View File

@@ -1,5 +1,5 @@
package emu.grasscutter.plugin.api;
public enum Item {
/* TODO: Use handbook to generate an Item enum. */
}
package emu.grasscutter.plugin.api;
public enum Item {
/* TODO: Use handbook to generate an Item enum. */
}

View File

@@ -1,124 +1,122 @@
package emu.grasscutter.plugin.api;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify;
import emu.grasscutter.utils.Position;
/**
* Hooks into the {@link Player} class, adding convenient ways to do certain things.
*/
public final class PlayerHook {
private final Player player;
/**
* Hooks into the player.
*
* @param player The player to hook into.
*/
public PlayerHook(Player player) {
this.player = player;
}
/**
* Kicks a player from the server.
* TODO: Refactor to kick using a packet.
*/
public void kick() {
this.player.getSession().close();
}
/**
* Sends a player to another scene.
*
* @param sceneId The scene to send the player to.
*/
public void changeScenes(int sceneId) {
this.player.getWorld().transferPlayerToScene(this.player, sceneId, this.player.getPosition());
}
/**
* Broadcasts an avatar property notify to all world players.
*
* @param property The property that was updated.
*/
public void updateFightProperty(FightProperty property) {
this.broadcastPacketToWorld(new PacketAvatarFightPropUpdateNotify(this.getCurrentAvatar(), property));
}
/**
* Broadcasts the packet sent to all world players.
*
* @param packet The packet to send.
*/
public void broadcastPacketToWorld(BasePacket packet) {
this.player.getWorld().broadcastPacket(packet);
}
/**
* Set the currently equipped avatar's health.
*
* @param health The health to set the avatar to.
*/
public void setHealth(float health) {
this.getCurrentAvatarEntity().setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, health);
this.updateFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
}
/**
* Revives the specified avatar.
*
* @param avatar The avatar to revive.
*/
public void reviveAvatar(Avatar avatar) {
this.broadcastPacketToWorld(new PacketAvatarLifeStateChangeNotify(avatar));
}
/**
* Teleports a player to a position.
* This will **not** transfer the player to another scene.
*
* @param position The position to teleport the player to.
*/
public void teleport(Position position) {
this.player.getPosition().set(position);
this.player.sendPacket(new PacketPlayerEnterSceneNotify(this.player,
EnterType.ENTER_TYPE_JUMP, EnterReason.TransPoint,
this.player.getSceneId(), position
));
}
/**
* Gets the currently selected avatar's max health.
*
* @return The max health as a float.
*/
public float getMaxHealth() {
return this.getCurrentAvatarEntity().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
}
/**
* Gets the currently selected avatar in entity form.
*
* @return The avatar as an {@link EntityAvatar}.
*/
public EntityAvatar getCurrentAvatarEntity() {
return this.player.getTeamManager().getCurrentAvatarEntity();
}
/**
* Gets the currently selected avatar.
*
* @return The avatar as an {@link Avatar}.
*/
public Avatar getCurrentAvatar() {
return this.getCurrentAvatarEntity().getAvatar();
}
}
package emu.grasscutter.plugin.api;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify;
import emu.grasscutter.server.packet.send.PacketPlayerEnterSceneNotify;
import emu.grasscutter.utils.Position;
/** Hooks into the {@link Player} class, adding convenient ways to do certain things. */
public final class PlayerHook {
private final Player player;
/**
* Hooks into the player.
*
* @param player The player to hook into.
*/
public PlayerHook(Player player) {
this.player = player;
}
/** Kicks a player from the server. TODO: Refactor to kick using a packet. */
public void kick() {
this.player.getSession().close();
}
/**
* Sends a player to another scene.
*
* @param sceneId The scene to send the player to.
*/
public void changeScenes(int sceneId) {
this.player.getWorld().transferPlayerToScene(this.player, sceneId, this.player.getPosition());
}
/**
* Broadcasts an avatar property notify to all world players.
*
* @param property The property that was updated.
*/
public void updateFightProperty(FightProperty property) {
this.broadcastPacketToWorld(
new PacketAvatarFightPropUpdateNotify(this.getCurrentAvatar(), property));
}
/**
* Broadcasts the packet sent to all world players.
*
* @param packet The packet to send.
*/
public void broadcastPacketToWorld(BasePacket packet) {
this.player.getWorld().broadcastPacket(packet);
}
/**
* Set the currently equipped avatar's health.
*
* @param health The health to set the avatar to.
*/
public void setHealth(float health) {
this.getCurrentAvatarEntity().setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, health);
this.updateFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
}
/**
* Revives the specified avatar.
*
* @param avatar The avatar to revive.
*/
public void reviveAvatar(Avatar avatar) {
this.broadcastPacketToWorld(new PacketAvatarLifeStateChangeNotify(avatar));
}
/**
* Teleports a player to a position. This will **not** transfer the player to another scene.
*
* @param position The position to teleport the player to.
*/
public void teleport(Position position) {
this.player.getPosition().set(position);
this.player.sendPacket(
new PacketPlayerEnterSceneNotify(
this.player,
EnterType.ENTER_TYPE_JUMP,
EnterReason.TransPoint,
this.player.getSceneId(),
position));
}
/**
* Gets the currently selected avatar's max health.
*
* @return The max health as a float.
*/
public float getMaxHealth() {
return this.getCurrentAvatarEntity().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
}
/**
* Gets the currently selected avatar in entity form.
*
* @return The avatar as an {@link EntityAvatar}.
*/
public EntityAvatar getCurrentAvatarEntity() {
return this.player.getTeamManager().getCurrentAvatarEntity();
}
/**
* Gets the currently selected avatar.
*
* @return The avatar as an {@link Avatar}.
*/
public Avatar getCurrentAvatar() {
return this.getCurrentAvatarEntity().getAvatar();
}
}

View File

@@ -1,129 +1,126 @@
package emu.grasscutter.plugin.api;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.command.PermissionHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.http.HttpServer;
import emu.grasscutter.server.http.Router;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* Hooks into the {@link GameServer} class, adding convenient ways to do certain things.
*/
public final class ServerHook {
private static ServerHook instance;
private final GameServer gameServer;
private final HttpServer httpServer;
/**
* Hooks into a server.
*
* @param gameServer The game server to hook into.
* @param httpServer The HTTP server to hook into.
*/
public ServerHook(GameServer gameServer, HttpServer httpServer) {
this.gameServer = gameServer;
this.httpServer = httpServer;
instance = this;
}
/**
* Gets the server hook instance.
*
* @return A {@link ServerHook} singleton.
*/
public static ServerHook getInstance() {
return instance;
}
/**
* @return The game server.
*/
public GameServer getGameServer() {
return this.gameServer;
}
/**
* @return The HTTP server.
*/
public HttpServer getHttpServer() {
return this.httpServer;
}
/**
* Gets all online players.
*
* @return Players connected to the server.
*/
@Deprecated(forRemoval = true)
public List<Player> getOnlinePlayers() {
return new ArrayList<>(this.gameServer.getPlayers().values());
}
/**
* Gets all online players.
*
* @return Players connected to the server.
*/
public Stream<Player> getOnlinePlayersStream() {
return this.gameServer.getPlayers().values().stream();
}
/**
* Registers a command to the {@link emu.grasscutter.command.CommandMap}.
*
* @param handler The command handler.
*/
public void registerCommand(CommandHandler handler) {
Class<? extends CommandHandler> clazz = handler.getClass();
if (!clazz.isAnnotationPresent(Command.class))
throw new IllegalArgumentException("Command handler must be annotated with @Command.");
Command commandData = clazz.getAnnotation(Command.class);
CommandMap.getInstance().registerCommand(commandData.label(), handler);
}
/**
* Adds a router using an instance of a class.
*
* @param router A router instance.
*/
public void addRouter(Router router) {
this.addRouter(router.getClass());
}
/**
* Adds a router using a class.
*
* @param router The class of the router.
*/
public void addRouter(Class<? extends Router> router) {
this.httpServer.addRouter(router);
}
/**
* Sets the server's authentication system.
*
* @param authSystem An instance of the authentication system.
*/
public void setAuthSystem(AuthenticationSystem authSystem) {
Grasscutter.setAuthenticationSystem(authSystem);
}
/**
* Sets the server's permission handler.
*
* @param permHandler An instance of the permission handler.
*/
public void setPermissionHandler(PermissionHandler permHandler) {
Grasscutter.setPermissionHandler(permHandler);
}
}
package emu.grasscutter.plugin.api;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.command.PermissionHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.http.HttpServer;
import emu.grasscutter.server.http.Router;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/** Hooks into the {@link GameServer} class, adding convenient ways to do certain things. */
public final class ServerHook {
private static ServerHook instance;
private final GameServer gameServer;
private final HttpServer httpServer;
/**
* Hooks into a server.
*
* @param gameServer The game server to hook into.
* @param httpServer The HTTP server to hook into.
*/
public ServerHook(GameServer gameServer, HttpServer httpServer) {
this.gameServer = gameServer;
this.httpServer = httpServer;
instance = this;
}
/**
* Gets the server hook instance.
*
* @return A {@link ServerHook} singleton.
*/
public static ServerHook getInstance() {
return instance;
}
/**
* @return The game server.
*/
public GameServer getGameServer() {
return this.gameServer;
}
/**
* @return The HTTP server.
*/
public HttpServer getHttpServer() {
return this.httpServer;
}
/**
* Gets all online players.
*
* @return Players connected to the server.
*/
@Deprecated(forRemoval = true)
public List<Player> getOnlinePlayers() {
return new ArrayList<>(this.gameServer.getPlayers().values());
}
/**
* Gets all online players.
*
* @return Players connected to the server.
*/
public Stream<Player> getOnlinePlayersStream() {
return this.gameServer.getPlayers().values().stream();
}
/**
* Registers a command to the {@link emu.grasscutter.command.CommandMap}.
*
* @param handler The command handler.
*/
public void registerCommand(CommandHandler handler) {
Class<? extends CommandHandler> clazz = handler.getClass();
if (!clazz.isAnnotationPresent(Command.class))
throw new IllegalArgumentException("Command handler must be annotated with @Command.");
Command commandData = clazz.getAnnotation(Command.class);
CommandMap.getInstance().registerCommand(commandData.label(), handler);
}
/**
* Adds a router using an instance of a class.
*
* @param router A router instance.
*/
public void addRouter(Router router) {
this.addRouter(router.getClass());
}
/**
* Adds a router using a class.
*
* @param router The class of the router.
*/
public void addRouter(Class<? extends Router> router) {
this.httpServer.addRouter(router);
}
/**
* Sets the server's authentication system.
*
* @param authSystem An instance of the authentication system.
*/
public void setAuthSystem(AuthenticationSystem authSystem) {
Grasscutter.setAuthenticationSystem(authSystem);
}
/**
* Sets the server's permission handler.
*
* @param permHandler An instance of the permission handler.
*/
public void setPermissionHandler(PermissionHandler permHandler) {
Grasscutter.setPermissionHandler(permHandler);
}
}