/* * Iris is a World Generator for Minecraft Bukkit Servers * Copyright (c) 2021 Arcane Arts (Volmit Software) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.volmit.iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.ServerConfigurator; import com.volmit.iris.core.link.IrisPapiExpansion; import com.volmit.iris.core.link.MultiverseCoreLink; import com.volmit.iris.core.link.MythicMobsLink; import com.volmit.iris.core.link.OraxenLink; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.engine.object.IrisCompat; import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.engine.object.IrisWorld; import com.volmit.iris.engine.platform.BukkitChunkGenerator; import com.volmit.iris.engine.platform.DummyChunkGenerator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.exceptions.IrisException; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.function.NastyRunnable; import com.volmit.iris.util.io.FileWatcher; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.io.InstanceState; import com.volmit.iris.util.io.JarScanner; import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.matter.MatterTest; import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.IrisService; import com.volmit.iris.util.plugin.Metrics; import com.volmit.iris.util.plugin.VolmitPlugin; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.reflect.ShadeFix; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.Queue; import com.volmit.iris.util.scheduling.ShurikenQueue; import io.papermc.lib.PaperLib; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.serializer.ComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.generator.ChunkGenerator; import org.bukkit.plugin.IllegalPluginAccessException; import org.bukkit.plugin.Plugin; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Annotation; import java.net.URL; import java.util.Date; import java.util.Map; @SuppressWarnings("CanBeFinal") public class Iris extends VolmitPlugin implements Listener { private static final Queue syncJobs = new ShurikenQueue<>(); public static Iris instance; public static BukkitAudiences audiences; public static MultiverseCoreLink linkMultiverseCore; public static OraxenLink linkOraxen; public static MythicMobsLink linkMythicMobs; public static IrisCompat compat; public static FileWatcher configWatcher; private static VolmitSender sender; static { try { InstanceState.updateInstanceId(); } catch (Throwable ignored) { } } private final KList postShutdown = new KList<>(); private KMap, IrisService> services; public Iris() { preEnable(); } public static VolmitSender getSender() { return sender; } @SuppressWarnings("unchecked") public static T service(Class c) { return (T) instance.services.get(c); } public static void callEvent(Event e) { if (!e.isAsynchronous()) { J.s(() -> Bukkit.getPluginManager().callEvent(e)); } else { Bukkit.getPluginManager().callEvent(e); } } public static KList initialize(String s, Class slicedClass) { JarScanner js = new JarScanner(instance.getJarFile(), s); KList v = new KList<>(); J.attempt(js::scan); for (Class i : js.getClasses()) { if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { try { v.add(i.getDeclaredConstructor().newInstance()); } catch (Throwable ignored) { } } } return v; } public static KList> getClasses(String s, Class slicedClass) { JarScanner js = new JarScanner(instance.getJarFile(), s); KList> v = new KList<>(); J.attempt(js::scan); for (Class i : js.getClasses()) { if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { try { v.add(i); } catch (Throwable ignored) { } } } return v; } public static KList initialize(String s) { return initialize(s, null); } public static void sq(Runnable r) { synchronized (syncJobs) { syncJobs.queue(r); } } public static File getTemp() { return instance.getDataFolder("cache", "temp"); } public static void msg(String string) { try { sender.sendMessage(string); } catch (Throwable e) { try { System.out.println(instance.getTag() + string.replaceAll("(<([^>]+)>)", "")); } catch (Throwable ignored1) { } } } public static File getCached(String name, String url) { String h = IO.hash(name + "@" + url); File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); if (!f.exists()) { try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { byte[] dataBuffer = new byte[1024]; int bytesRead; while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { fileOutputStream.write(dataBuffer, 0, bytesRead); Iris.verbose("Aquiring " + name); } } catch (IOException e) { Iris.reportError(e); } } return f.exists() ? f : null; } public static String getNonCached(String name, String url) { String h = IO.hash(name + "*" + url); File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { byte[] dataBuffer = new byte[1024]; int bytesRead; while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { fileOutputStream.write(dataBuffer, 0, bytesRead); } } catch (IOException e) { Iris.reportError(e); } try { return IO.readAll(f); } catch (IOException e) { Iris.reportError(e); } return ""; } public static File getNonCachedFile(String name, String url) { String h = IO.hash(name + "*" + url); File f = Iris.instance.getDataFile("cache", h.substring(0, 2), h.substring(3, 5), h); Iris.verbose("Download " + name + " -> " + url); try (BufferedInputStream in = new BufferedInputStream(new URL(url).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(f)) { byte[] dataBuffer = new byte[1024]; int bytesRead; while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { fileOutputStream.write(dataBuffer, 0, bytesRead); } fileOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); Iris.reportError(e); } return f; } public static void warn(String string) { msg(C.YELLOW + string); } public static void error(String string) { msg(C.RED + string); } public static void debug(String string) { if (!IrisSettings.get().getGeneral().isDebug()) { return; } try { throw new RuntimeException(); } catch (Throwable e) { try { String[] cc = e.getStackTrace()[1].getClassName().split("\\Q.\\E"); if (cc.length > 5) { debug(cc[3] + "/" + cc[4] + "/" + cc[cc.length - 1], e.getStackTrace()[1].getLineNumber(), string); } else { debug(cc[3] + "/" + cc[4], e.getStackTrace()[1].getLineNumber(), string); } } catch (Throwable ex) { debug("Origin", -1, string); } } } public static void debug(String category, int line, String string) { if (!IrisSettings.get().getGeneral().isDebug()) { return; } msg("" + category + " <#bf3b76>" + line + " " + C.LIGHT_PURPLE + string.replaceAll("\\Q<\\E", "[").replaceAll("\\Q>\\E", "]")); } public static void verbose(String string) { debug(string); } public static void success(String string) { msg(C.IRIS + string); } public static void info(String string) { msg(C.WHITE + string); } @SuppressWarnings("deprecation") public static void later(NastyRunnable object) { try { Bukkit.getScheduler().scheduleAsyncDelayedTask(instance, () -> { try { object.run(); } catch (Throwable e) { e.printStackTrace(); Iris.reportError(e); } }, RNG.r.i(100, 1200)); } catch(IllegalPluginAccessException ignored) { } } public static int jobCount() { return syncJobs.size(); } public static void clearQueues() { synchronized (syncJobs) { syncJobs.clear(); } } private static int getJavaVersion() { String version = System.getProperty("java.version"); if (version.startsWith("1.")) { version = version.substring(2, 3); } else { int dot = version.indexOf("."); if (dot != -1) { version = version.substring(0, dot); } } return Integer.parseInt(version); } public static void reportErrorChunk(int x, int z, Throwable e, String extra) { if (IrisSettings.get().getGeneral().isDebug()) { File f = instance.getDataFile("debug", "chunk-errors", "chunk." + x + "." + z + ".txt"); if (!f.exists()) { J.attempt(() -> { PrintWriter pw = new PrintWriter(f); pw.println("Thread: " + Thread.currentThread().getName()); pw.println("First: " + new Date(M.ms())); e.printStackTrace(pw); pw.close(); }); } Iris.debug("Chunk " + x + "," + z + " Exception Logged: " + e.getClass().getSimpleName() + ": " + C.RESET + "" + C.LIGHT_PURPLE + e.getMessage()); } } public static void reportError(Throwable e) { if (IrisSettings.get().getGeneral().isDebug()) { String n = e.getClass().getCanonicalName() + "-" + e.getStackTrace()[0].getClassName() + "-" + e.getStackTrace()[0].getLineNumber(); if (e.getCause() != null) { n += "-" + e.getCause().getStackTrace()[0].getClassName() + "-" + e.getCause().getStackTrace()[0].getLineNumber(); } File f = instance.getDataFile("debug", "caught-exceptions", n + ".txt"); if (!f.exists()) { J.attempt(() -> { PrintWriter pw = new PrintWriter(f); pw.println("Thread: " + Thread.currentThread().getName()); pw.println("First: " + new Date(M.ms())); e.printStackTrace(pw); pw.close(); }); } Iris.debug("Exception Logged: " + e.getClass().getSimpleName() + ": " + C.RESET + "" + C.LIGHT_PURPLE + e.getMessage()); } } public static void dump() { try { File fi = Iris.instance.getDataFile("dump", "td-" + new java.sql.Date(M.ms()) + ".txt"); FileOutputStream fos = new FileOutputStream(fi); Map f = Thread.getAllStackTraces(); PrintWriter pw = new PrintWriter(fos); for (Thread i : f.keySet()) { pw.println("========================================"); pw.println("Thread: '" + i.getName() + "' ID: " + i.getId() + " STATUS: " + i.getState().name()); for (StackTraceElement j : f.get(i)) { pw.println(" @ " + j.toString()); } pw.println("========================================"); pw.println(); pw.println(); } pw.close(); System.out.println("DUMPED! See " + fi.getAbsolutePath()); } catch (Throwable e) { e.printStackTrace(); } } private void preEnable() { instance = this; services = new KMap<>(); initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class) i.getClass(), (IrisService) i)); INMS.get(); IO.delete(new File("iris")); fixShading(); } private void enable() { setupAudience(); sender = new VolmitSender(Bukkit.getConsoleSender()); sender.setTag(getTag()); instance = this; compat = IrisCompat.configured(getDataFile("compat.json")); linkMultiverseCore = new MultiverseCoreLink(); linkOraxen = new OraxenLink(); linkMythicMobs = new MythicMobsLink(); configWatcher = new FileWatcher(getDataFile("settings.json")); services.values().forEach(IrisService::onEnable); services.values().forEach(this::registerListener); } private void setupAudience() { try { audiences = BukkitAudiences.create(this); } catch (Throwable e) { e.printStackTrace(); IrisSettings.get().getGeneral().setUseConsoleCustomColors(false); IrisSettings.get().getGeneral().setUseCustomColorsIngame(false); Iris.error("Failed to setup Adventure API... No custom colors :("); } } public void postShutdown(Runnable r) { postShutdown.add(r); } private void postEnable() { J.a(() -> PaperLib.suggestPaper(this)); J.a(() -> IO.delete(getTemp())); J.a(this::bstats); J.ar(this::checkConfigHotload, 60); J.sr(this::tickQueue, 0); J.s(this::setupPapi); J.a(ServerConfigurator::configure, 20); splash(); J.a(MatterTest::test, 20); if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) { Iris.info("Starting up auto Studio!"); try { Player r = new KList<>(getServer().getOnlinePlayers()).getRandom(); Iris.service(StudioSVC.class).open(r != null ? new VolmitSender(r) : sender, 1337, IrisSettings.get().getGenerator().getDefaultWorldType(), (w) -> { J.s(() -> { for (Player i : getServer().getOnlinePlayers()) { i.setGameMode(GameMode.SPECTATOR); i.teleport(new Location(w, 0, 200, 0)); } }); }); } catch (IrisException e) { e.printStackTrace(); } } } @SuppressWarnings("unchecked") public void onEnable() { enable(); super.onEnable(); Bukkit.getPluginManager().registerEvents(this, this); J.s(this::postEnable); } public void onDisable() { services.values().forEach(IrisService::onDisable); Bukkit.getScheduler().cancelTasks(this); HandlerList.unregisterAll((Plugin) this); postShutdown.forEach(Runnable::run); services.clear(); MultiBurst.burst.close(); super.onDisable(); } private void fixShading() { ShadeFix.fix(ComponentSerializer.class); } private void setupPapi() { if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { new IrisPapiExpansion().register(); } } @Override public void start() { } @Override public void stop() { } @Override public String getTag(String subTag) { return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.IRIS + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; } private void checkConfigHotload() { if (configWatcher.checkModified()) { IrisSettings.invalidate(); IrisSettings.get(); configWatcher.checkModified(); Iris.info("Hotloaded settings.json "); } } private void tickQueue() { synchronized (Iris.syncJobs) { if (!Iris.syncJobs.hasNext()) { return; } long ms = M.ms(); while (Iris.syncJobs.hasNext() && M.ms() - ms < 25) { try { Iris.syncJobs.next().run(); } catch (Throwable e) { e.printStackTrace(); Iris.reportError(e); } } } } private void bstats() { if (IrisSettings.get().getGeneral().isPluginMetrics()) { J.s(() -> new Metrics(Iris.instance, 8757)); } } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { return super.onCommand(sender, command, label, args); } public void imsg(CommandSender s, String msg) { s.sendMessage(C.IRIS + "[" + C.DARK_GRAY + "Iris" + C.IRIS + "]" + C.GRAY + ": " + msg); } @Override public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { if (worldName.equals("test")) { try { throw new RuntimeException(); } catch (Throwable e) { Iris.info(e.getStackTrace()[1].getClassName()); if (e.getStackTrace()[1].getClassName().contains("com.onarandombox.MultiverseCore")) { Iris.debug("MVC Test detected, Quick! Send them the dummy!"); return new DummyChunkGenerator(); } } } IrisDimension dim; if (id == null || id.isEmpty()) { dim = IrisData.loadAnyDimension(IrisSettings.get().getGenerator().getDefaultWorldType()); } else { dim = IrisData.loadAnyDimension(id); } Iris.debug("Generator ID: " + id + " requested by bukkit/plugin"); if (dim == null) { Iris.warn("Unable to find dimension type " + id + " Looking for online packs..."); service(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, true); dim = IrisData.loadAnyDimension(id); if (dim == null) { throw new RuntimeException("Can't find dimension " + id + "!"); } else { Iris.info("Resolved missing dimension, proceeding with generation."); } } Iris.debug("Assuming IrisDimension: " + dim.getName()); IrisWorld w = IrisWorld.builder() .name(worldName) .seed(1337) .environment(dim.getEnvironment()) .worldFolder(new File(worldName)) .minHeight(0) .maxHeight(256) .build(); Iris.debug("Generator Config: " + w.toString()); File ff = new File(w.worldFolder(), "iris/pack"); if (!ff.exists() || ff.listFiles().length == 0) { ff.mkdirs(); service(StudioSVC.class).installIntoWorld(sender, dim.getLoadKey(), ff.getParentFile()); } return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey()); } public void splash() { if (!IrisSettings.get().getGeneral().isSplashLogoStartup()) { return; } // @NoArgsConstructor String padd = Form.repeat(" ", 8); String padd2 = Form.repeat(" ", 4); String[] info = {"", "", "", "", "", padd2 + C.IRIS + " Iris", padd2 + C.GRAY + " by " + "Volmit Software", padd2 + C.GRAY + " v" + C.IRIS + getDescription().getVersion(), }; String[] splash = {padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + C.IRIS + " .(((()))). ", padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + C.IRIS + " .((((((())))))). ", padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + C.IRIS + " ((((((((())))))))) " + C.GRAY + " @", padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + C.IRIS + " ((((((((-))))))))) " + C.GRAY + " @@", padd + C.GRAY + "@@@&&" + C.IRIS + " ((((((({ })))))))) " + C.GRAY + " &&@@@", padd + C.GRAY + "@@" + C.IRIS + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", padd + C.GRAY + "@" + C.IRIS + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", padd + C.GRAY + "" + C.IRIS + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", padd + C.GRAY + "" + C.IRIS + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" }; //@done Iris.info("Server type & version: " + Bukkit.getVersion()); Iris.info("Bukkit version: " + Bukkit.getBukkitVersion()); Iris.info("Java version: " + getJavaVersion()); Iris.info("Custom Biomes: " + INMS.get().countCustomBiomes()); for (int i = 0; i < info.length; i++) { splash[i] += info[i]; } Iris.info("\n\n " + new KList<>(splash).toString("\n") + "\n"); } public boolean isMCA() { return IrisSettings.get().getGenerator().isHeadlessPregeneration(); } }