diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/addon/AddonLoader.java b/src/main/java/com/github/skriptdev/skript/api/skript/addon/AddonLoader.java new file mode 100644 index 00000000..1c7707f7 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/addon/AddonLoader.java @@ -0,0 +1,114 @@ +package com.github.skriptdev.skript.api.skript.addon; + +import com.github.skriptdev.skript.api.utils.Utils; +import com.github.skriptdev.skript.plugin.HySk; +import com.hypixel.hytale.codec.ExtraInfo; +import com.hypixel.hytale.codec.util.RawJsonReader; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.log.LogEntry; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.registration.SkriptAddon; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class AddonLoader { + + private final SkriptLogger logger; + + public AddonLoader(SkriptLogger logger) { + this.logger = logger; + } + + public void loadAddonsFromFolder() { + Path resolve = HySk.getInstance().getDataDirectory().resolve("addons"); + File addonFolder = resolve.toFile(); + if (!addonFolder.exists()) { + if (!addonFolder.mkdirs()) { + this.logger.error("Failed to create addons folder", ErrorType.STRUCTURE_ERROR); + return; + } + } else if (!addonFolder.isDirectory()) { + this.logger.error("Addons folder is not a directory", ErrorType.STRUCTURE_ERROR); + return; + } + File[] files = addonFolder.listFiles(); + if (files == null) { + this.logger.error("Failed to list files in addons folder", ErrorType.STRUCTURE_ERROR); + return; + } + + for (File file : Arrays.stream(files) + .filter(File::isFile) + .filter(f -> f.getName().endsWith(".jar")) + .toList()) { + loadAddon(file); + } + } + + private void loadAddon(File file) { + try (JarFile jarFile = new JarFile(file)) { + JarEntry jarEntry = jarFile.getJarEntry("manifest.json"); + if (jarEntry == null) { + this.logger.error("Manifest.json not found in addon " + file.getName(), ErrorType.STRUCTURE_ERROR); + return; + } + + InputStream inputStream = jarFile.getInputStream(jarEntry); + InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); + char[] buffer = RawJsonReader.READ_BUFFER.get(); + RawJsonReader rawJsonReader = new RawJsonReader(reader, buffer); + + Manifest manifest = Manifest.CODEC.decodeJson(rawJsonReader, new ExtraInfo()); + if (manifest == null) { + this.logger.error("Failed to decode manifest.json in addon " + file.getName(), ErrorType.STRUCTURE_ERROR); + return; + } + + URL[] urls = {file.toURI().toURL()}; + URLClassLoader classLoader = new URLClassLoader(urls, HySk.getInstance().getClassLoader()); + Class externalClass; + try { + externalClass = classLoader.loadClass(manifest.getMainClass()); + } catch (ClassNotFoundException e) { + this.logger.error("Main class not found in addon " + file.getName(), ErrorType.STRUCTURE_ERROR); + return; + } + Object mainClassIntance; + try { + mainClassIntance = externalClass.getDeclaredConstructor(String.class).newInstance(manifest.getName()); + } catch (ReflectiveOperationException e) { + this.logger.error("Failed to create instance of addon " + file.getName(), ErrorType.EXCEPTION); + return; + } + if (mainClassIntance instanceof HySkriptAddon addon) { + addon.setManifest(manifest); + addon.setup(); + // Finalize registration and logging + for (LogEntry logEntry : addon.getSkriptRegistration().register()) { + Utils.log(null, logEntry); + } + } + } catch (IOException e) { + this.logger.error("Failed to load addon " + file.getName(), ErrorType.EXCEPTION); + } + } + + public void shutdownAddons() { + for (SkriptAddon addon : SkriptAddon.getAddons()) { + if (addon instanceof HySkriptAddon hySkriptAddon) { + hySkriptAddon.shutdown(); + } + } + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/addon/HySkriptAddon.java b/src/main/java/com/github/skriptdev/skript/api/skript/addon/HySkriptAddon.java new file mode 100644 index 00000000..5e991674 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/addon/HySkriptAddon.java @@ -0,0 +1,57 @@ +package com.github.skriptdev.skript.api.skript.addon; + +import com.hypixel.hytale.server.core.Message; +import io.github.syst3ms.skriptparser.registration.SkriptAddon; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Base class for addons for HySkript. + */ +public abstract class HySkriptAddon extends SkriptAddon { + + private Manifest manifest; + + public HySkriptAddon(String name) { + super(name); + } + + /** + * Called when the addon starts to load. + * This is a good time to set up your syntaxes. + */ + abstract public void setup(); + + /** + * Called when the addon is shutting down. + * This is a good time to clean up resources. + */ + abstract public void shutdown(); + + final void setManifest(Manifest manifest) { + this.manifest = manifest; + } + + public final Manifest getManifest() { + return manifest; + } + + public final Message[] getInfo() { + List info = new ArrayList<>(); + info.add(Message.raw("Version: " + this.manifest.getVersion())); + + String description = this.manifest.getDescription(); + if (description != null) info.add(Message.raw("Description: " + description)); + + @Nullable String[] authors = this.manifest.getAuthors(); + if (authors != null) info.add(Message.raw("Authors: " + String.join(", ", authors))); + + String website = this.manifest.getWebsite(); + if (website != null) info.add(Message.raw("Website: ").insert(Message.raw(website).link(website))); + + return info.toArray(Message[]::new); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/addon/Manifest.java b/src/main/java/com/github/skriptdev/skript/api/skript/addon/Manifest.java new file mode 100644 index 00000000..41584760 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/addon/Manifest.java @@ -0,0 +1,76 @@ +package com.github.skriptdev.skript.api.skript.addon; + +import com.hypixel.hytale.codec.Codec; +import com.hypixel.hytale.codec.KeyedCodec; +import com.hypixel.hytale.codec.builder.BuilderCodec; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; + +public class Manifest { + + private static final BuilderCodec.Builder BUILDER = BuilderCodec.builder(Manifest.class, Manifest::new); + public static final Codec CODEC = BUILDER + .append(new KeyedCodec<>("Main", Codec.STRING), + ((manifest, string) -> manifest.mainClass = string), + (manifest -> manifest.mainClass)) + .add().append(new KeyedCodec<>("Name", Codec.STRING), + ((manifest, string) -> manifest.name = string), + (manifest -> manifest.name)) + .add().append(new KeyedCodec<>("Version", Codec.STRING), + ((manifest, string) -> manifest.version = string), + (manifest -> manifest.version)) + .add().append(new KeyedCodec<>("Description", Codec.STRING), + ((manifest, string) -> manifest.description = string), + (manifest -> manifest.description)) + .add().append(new KeyedCodec<>("Authors", Codec.STRING_ARRAY), + ((manifest, strings) -> manifest.authors = strings), + (manifest -> manifest.authors)) + .add().append(new KeyedCodec<>("Website", Codec.STRING), + ((manifest, string) -> manifest.website = string), + (manifest -> manifest.website)) + .add().build(); + private String mainClass; + private String name; + private String version; + private String description; + private String[] authors; + private String website; + + public String getMainClass() { + return this.mainClass; + } + + public String getName() { + return this.name; + } + + public String getVersion() { + return this.version; + } + + public String getDescription() { + return this.description; + } + + public String[] getAuthors() { + return this.authors; + } + + public String getWebsite() { + return this.website; + } + + @Override + public String toString() { + return "Manifest{" + + "name='" + name + '\'' + + ", version='" + version + '\'' + + ", description='" + description + '\'' + + ", authors=" + Arrays.toString(authors) + + ", website='" + website + '\'' + + '}'; + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/Skript.java b/src/main/java/com/github/skriptdev/skript/plugin/Skript.java index 62022aef..589f29db 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/Skript.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/Skript.java @@ -1,6 +1,7 @@ package com.github.skriptdev.skript.plugin; import com.github.skriptdev.skript.api.skript.ScriptsLoader; +import com.github.skriptdev.skript.api.skript.addon.AddonLoader; import com.github.skriptdev.skript.api.skript.command.ArgUtils; import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; import com.github.skriptdev.skript.api.skript.variables.JsonVariableStorage; @@ -36,6 +37,7 @@ public class Skript extends SkriptAddon { private final SkriptLogger logger; private SkriptRegistration registration; private ElementRegistration elementRegistration; + private AddonLoader addonLoader; private ScriptsLoader scriptsLoader; Skript(HySk hySk) { @@ -80,6 +82,12 @@ private void setup() { printSyntaxCount(); Utils.log("HySkript setup complete!"); + // LOAD ADDONS + this.logger.info("Loading addons..."); + this.addonLoader = new AddonLoader(this.logger); + this.addonLoader.loadAddonsFromFolder(); + this.logger.info("Finished loading addons!"); + // LOAD VARIABLES loadVariables(); @@ -95,6 +103,9 @@ public void shutdown() { Utils.log("Saving variables..."); Variables.shutdown(); Utils.log("Variable saving complete!"); + + // SHUTDOWN ADDONS + this.addonLoader.shutdownAddons(); } private void printSyntaxCount() { @@ -194,4 +205,13 @@ public ScriptsLoader getScriptsLoader() { return this.scriptsLoader; } + /** + * Get an instance of Skript. + * + * @return Instance of Skript. + */ + public static Skript getInstance() { + return INSTANCE; + } + } diff --git a/src/main/java/com/github/skriptdev/skript/plugin/command/SkriptCommand.java b/src/main/java/com/github/skriptdev/skript/plugin/command/SkriptCommand.java index 1e50b08d..e9e9d19e 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/command/SkriptCommand.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/command/SkriptCommand.java @@ -1,5 +1,6 @@ package com.github.skriptdev.skript.plugin.command; +import com.github.skriptdev.skript.api.skript.addon.HySkriptAddon; import com.github.skriptdev.skript.api.skript.docs.JsonDocPrinter; import com.github.skriptdev.skript.api.skript.docs.MarkdownDocPrinter; import com.github.skriptdev.skript.api.utils.Utils; @@ -10,6 +11,7 @@ import com.hypixel.hytale.server.core.command.system.AbstractCommand; import com.hypixel.hytale.server.core.command.system.CommandContext; import com.hypixel.hytale.server.core.command.system.CommandRegistry; +import com.hypixel.hytale.server.core.command.system.CommandSender; import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg; import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg; import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg; @@ -39,6 +41,7 @@ public SkriptCommand(CommandRegistry registry) { addAliases("sk"); // Keep these in alphabetical order + addSubCommand(addonsCommand()); addSubCommand(new DocsCommand()); addSubCommand(infoCommand()); addSubCommand(new ReloadCommand()); @@ -139,6 +142,30 @@ protected CompletableFuture execute(@NotNull CommandContext commandContext }; } + private AbstractCommand addonsCommand() { + return new AbstractCommand("addons", "Get info about loaded addons.") { + @Override + protected CompletableFuture execute(@NotNull CommandContext commandContext) { + return CompletableFuture.runAsync(() -> { + List addons = SkriptAddon.getAddons().stream() + .filter(addon -> !addon.getAddonName().equalsIgnoreCase("skript-parser") && !addon.getAddonName().equalsIgnoreCase("HySkript")) + .toList(); + if (addons.isEmpty()) return; + CommandSender sender = commandContext.sender(); + Utils.sendMessage(sender, "Loaded Addons:"); + addons.forEach(addon -> { + if (addon instanceof HySkriptAddon hySkriptAddon) { + Utils.sendMessage(sender, " - %s:", hySkriptAddon.getAddonName()); + for (Message s : hySkriptAddon.getInfo()) { + sender.sendMessage(Message.raw(" ").insert(s)); + } + } + }); + }); + } + }; + } + private void printInfo(IMessageReceiver sender) { Utils.sendMessage(sender, "HySkript Version: %s", HySk.getInstance().getManifest().getVersion()); Utils.sendMessage(sender, "Hytale Version: %s (%s)", ManifestUtil.getImplementationVersion(), ManifestUtil.getPatchline());