diff --git a/doc/Modding.md b/doc/Modding.md index 37556f016..83f6915e3 100644 --- a/doc/Modding.md +++ b/doc/Modding.md @@ -1,7 +1,7 @@ # Modding ## mod.json -In your jar file (the mod) must be a file called `mod.json`. +In the root folder of your jar file (the mod) must be a file called `mod.json`. It contains metadata for the mod loader. ### Example ```json { diff --git a/src/main/java/de/bixilon/minosoft/Config.java b/src/main/java/de/bixilon/minosoft/Config.java index 9c3cf5c60..3c10beac6 100644 --- a/src/main/java/de/bixilon/minosoft/Config.java +++ b/src/main/java/de/bixilon/minosoft/Config.java @@ -13,9 +13,34 @@ package de.bixilon.minosoft; +import de.bixilon.minosoft.util.OSUtil; + +import java.io.File; + public class Config { - public static final String configFileName = "config.json"; - public static final boolean skipAuthentication = false; // only for offline development - public static final boolean colorLog = true; + public static final String configFileName = "config.json"; // Path of the minosoft base configuration (located in AppData/Minosoft/config) + public static final boolean skipAuthentication = false; // disables all connections to mojang + public static final boolean colorLog = true; // the log should be colored with ANSI (does not affect chat components) + public static final boolean logRelativeTime = false; // prefix all log messages with the relative start time in milliseconds instead of the formatted time + public static String homeDir; + + static { + // Sets Config.homeDir to the correct folder per OS + homeDir = System.getProperty("user.home"); + if (!homeDir.endsWith(File.separator)) { + homeDir += "/"; + } + homeDir += switch (OSUtil.getOS()) { + case LINUX -> ".local/share/minosoft/"; + case WINDOWS -> "AppData/Roaming/Minosoft/"; + case MAC -> "Library/Application Support/Minosoft/"; + case OTHER -> ".minosoft/"; + }; + File folder = new File(homeDir); + if (!folder.exists() && !folder.mkdirs()) { + // failed creating folder + throw new RuntimeException(String.format("Could not create home folder (%s)!", homeDir)); + } + } } diff --git a/src/main/java/de/bixilon/minosoft/Launcher.java b/src/main/java/de/bixilon/minosoft/Launcher.java index f3b879822..dea22400c 100644 --- a/src/main/java/de/bixilon/minosoft/Launcher.java +++ b/src/main/java/de/bixilon/minosoft/Launcher.java @@ -20,12 +20,16 @@ import de.bixilon.minosoft.gui.main.Server; import de.bixilon.minosoft.gui.main.ServerListCell; import de.bixilon.minosoft.logging.Log; import javafx.application.Application; +import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; +import javafx.scene.control.Dialog; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; +import javafx.scene.control.ProgressBar; +import javafx.scene.layout.GridPane; import javafx.scene.layout.VBox; import javafx.stage.Stage; import javafx.util.Callback; @@ -33,14 +37,32 @@ import javafx.util.Callback; import java.io.IOException; public class Launcher extends Application { + private static ProgressBar progressBar; + private static Dialog progressDialog; public static void start() { + Log.info("Starting launcher..."); launch(); + Log.info("Launcher started!"); + } + + protected static void setProgressBar(int jobsLeft) { + Platform.runLater(() -> { + if (progressBar == null || progressDialog == null) { + return; + } + if (jobsLeft == 0) { + progressDialog.setResult(Boolean.TRUE); + progressDialog.close(); + return; + } + progressBar.setProgress(1.0F / jobsLeft); + }); } @Override public void start(Stage primaryStage) throws IOException { - Log.info("Starting launcher..."); + Log.info("Preparing main window..."); GUITools.versionList.setCellFactory(new Callback<>() { @Override @@ -74,6 +96,18 @@ public class Launcher extends Application { if (Minosoft.getSelectedAccount() == null) { MainWindow.manageAccounts(); } - Log.info("Launcher started!"); + Log.info("Main window prepared!"); + if (Minosoft.getStartUpJobsLeft() == 0) { + return; + } + progressDialog = new Dialog<>(); + progressDialog.setHeaderText("Minosoft is still starting up..."); + progressDialog.setTitle("Starting up"); + GridPane grid = new GridPane(); + progressBar = new ProgressBar(); + progressBar.setProgress(1.0D / 5); + grid.add(progressBar, 0, 0); + progressDialog.getDialogPane().setContent(grid); + progressDialog.show(); } } \ No newline at end of file diff --git a/src/main/java/de/bixilon/minosoft/Minosoft.java b/src/main/java/de/bixilon/minosoft/Minosoft.java index 7b8c3b413..06736a66c 100644 --- a/src/main/java/de/bixilon/minosoft/Minosoft.java +++ b/src/main/java/de/bixilon/minosoft/Minosoft.java @@ -18,34 +18,35 @@ import de.bixilon.minosoft.config.Configuration; import de.bixilon.minosoft.config.GameConfiguration; import de.bixilon.minosoft.game.datatypes.objectLoader.versions.Versions; import de.bixilon.minosoft.gui.main.AccountListCell; +import de.bixilon.minosoft.gui.main.MainWindow; import de.bixilon.minosoft.gui.main.Server; import de.bixilon.minosoft.logging.Log; import de.bixilon.minosoft.logging.LogLevels; import de.bixilon.minosoft.modding.event.EventManager; import de.bixilon.minosoft.modding.loading.ModLoader; -import de.bixilon.minosoft.util.OSUtil; import de.bixilon.minosoft.util.Util; import de.bixilon.minosoft.util.mojang.api.MojangAccount; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; public class Minosoft { + public static final HashSet eventManagers = new HashSet<>(); + private static final CountDownLatch startStatus = new CountDownLatch(2); // number of critical components (wait for them before other "big" actions) public static HashBiMap accountList; public static MojangAccount selectedAccount; public static ArrayList serverList; - public static final HashSet eventManagers = new HashSet<>(); - static Configuration config; + public static Configuration config; public static void main(String[] args) { // init log thread Log.initThread(); Log.info("Starting..."); - setConfigFolder(); Log.info("Reading config file..."); try { config = new Configuration(Config.configFileName); @@ -58,52 +59,49 @@ public class Minosoft { // set log level from config Log.setLevel(LogLevels.valueOf(config.getString(GameConfiguration.GENERAL_LOG_LEVEL))); Log.info(String.format("Logging info with level: %s", Log.getLevel())); - Log.info("Loading versions.json..."); - long mappingStartLoadingTime = System.currentTimeMillis(); - try { - Versions.load(Util.readJsonAsset("mapping/versions.json")); - } catch (IOException e) { - e.printStackTrace(); - System.exit(1); - } - Log.info(String.format("Loaded versions mapping in %dms", (System.currentTimeMillis() - mappingStartLoadingTime))); - - Log.debug("Refreshing client token..."); - checkClientToken(); - accountList = config.getMojangAccounts(); - selectAccount(accountList.get(config.getString(GameConfiguration.ACCOUNT_SELECTED))); serverList = config.getServers(); - new Thread(() -> { + ArrayList> startCallables = new ArrayList<>(); + startCallables.add(() -> { + Log.info("Loading versions.json..."); + long mappingStartLoadingTime = System.currentTimeMillis(); try { - ModLoader.loadMods(); - } catch (Exception exception) { - exception.printStackTrace(); + Versions.load(Util.readJsonAsset("mapping/versions.json")); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); } - }, "ModLoader").start(); - Launcher.start(); + Log.info(String.format("Loaded versions mapping in %dms", (System.currentTimeMillis() - mappingStartLoadingTime))); + countDownStart(); // (another) critical component was loaded + return true; + }); + startCallables.add(() -> { + Log.debug("Refreshing client token..."); + checkClientToken(); + accountList = config.getMojangAccounts(); + selectAccount(accountList.get(config.getString(GameConfiguration.ACCOUNT_SELECTED))); + return true; + }); + startCallables.add(() -> { + ModLoader.loadMods(); + countDownStart(); // (another) critical component was loaded + return true; + }); + + startCallables.add(() -> { + Launcher.start(); + return true; + }); + try { + Util.executeInThreadPool("Start", startCallables); + } catch (Exception e) { + e.printStackTrace(); + } } - /** - * Sets Config.homeDir to the correct folder per OS - */ - public static void setConfigFolder() { - String path = System.getProperty("user.home"); - if (!path.endsWith(File.separator)) { - path += "/"; - } - path += switch (OSUtil.getOS()) { - case LINUX -> ".local/share/minosoft/"; - case WINDOWS -> "AppData/Roaming/Minosoft/"; - case MAC -> "Library/Application Support/Minosoft/"; - case OTHER -> ".minosoft/"; - }; - File folder = new File(path); - if (!folder.exists() && !folder.mkdirs()) { - // failed creating folder - throw new RuntimeException(String.format("Could not create home folder (%s)!", path)); - } - Config.homeDir = path; + private static void countDownStart() { + startStatus.countDown(); + Launcher.setProgressBar((int) startStatus.getCount()); } public static void checkClientToken() { @@ -130,6 +128,9 @@ public class Minosoft { } config.putString(GameConfiguration.ACCOUNT_SELECTED, account.getUserId()); selectedAccount = account; + if (MainWindow.accountMenu2 != null) { + MainWindow.accountMenu2.setText(String.format("Account (%s)", account.getPlayerName())); + } account.saveToConfig(); } @@ -148,4 +149,19 @@ public class Minosoft { public static MojangAccount getSelectedAccount() { return selectedAccount; } + + /** + * Waits until all critical components are started + */ + public static void waitForStartup() { + try { + startStatus.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public static int getStartUpJobsLeft() { + return (int) startStatus.getCount(); + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/main/MainWindow.java b/src/main/java/de/bixilon/minosoft/gui/main/MainWindow.java index 93d3972a5..0ab1e06ae 100644 --- a/src/main/java/de/bixilon/minosoft/gui/main/MainWindow.java +++ b/src/main/java/de/bixilon/minosoft/gui/main/MainWindow.java @@ -42,9 +42,12 @@ public class MainWindow implements Initializable { @FXML public Menu accountMenu; + public static Menu accountMenu2; + @Override public void initialize(URL url, ResourceBundle resourceBundle) { serversPane.setCenter(ServerListCell.listView); + accountMenu2 = accountMenu; if (Minosoft.getSelectedAccount() != null) { accountMenu.setText(String.format("Account (%s)", Minosoft.getSelectedAccount().getPlayerName())); } diff --git a/src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java b/src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java index 2e0bf5b52..1b635b94f 100644 --- a/src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java +++ b/src/main/java/de/bixilon/minosoft/gui/main/ServerListCell.java @@ -41,7 +41,6 @@ import javafx.scene.text.Text; import javafx.scene.text.TextFlow; import javafx.stage.Modality; import javafx.stage.Stage; -import javafx.util.Pair; import java.io.IOException; import java.net.URL; @@ -349,7 +348,7 @@ public class ServerListCell extends ListCell implements Initializable { public void showInfo() { - Dialog> dialog = new Dialog<>(); + Dialog dialog = new Dialog<>(); dialog.setTitle("View server info: " + server.getName()); ButtonType loginButtonType = ButtonType.CLOSE; diff --git a/src/main/java/de/bixilon/minosoft/logging/Log.java b/src/main/java/de/bixilon/minosoft/logging/Log.java index c339cff81..f403a4a61 100644 --- a/src/main/java/de/bixilon/minosoft/logging/Log.java +++ b/src/main/java/de/bixilon/minosoft/logging/Log.java @@ -24,6 +24,7 @@ import java.util.concurrent.LinkedBlockingQueue; public class Log { final static SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); final static LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + final static long startTime = System.currentTimeMillis(); static LogLevels level = LogLevels.PROTOCOL; public static void initThread() { @@ -64,7 +65,11 @@ public class Log { } StringBuilder builder = new StringBuilder(); builder.append("["); - builder.append(timeFormat.format(System.currentTimeMillis())); + if (Config.logRelativeTime) { + builder.append(System.currentTimeMillis() - startTime); + } else { + builder.append(timeFormat.format(System.currentTimeMillis())); + } builder.append("] ["); builder.append(Thread.currentThread().getName()); builder.append("] ["); diff --git a/src/main/java/de/bixilon/minosoft/modding/loading/ModLoader.java b/src/main/java/de/bixilon/minosoft/modding/loading/ModLoader.java index 7305b39a4..d180e2b79 100644 --- a/src/main/java/de/bixilon/minosoft/modding/loading/ModLoader.java +++ b/src/main/java/de/bixilon/minosoft/modding/loading/ModLoader.java @@ -32,6 +32,7 @@ public class ModLoader { static final LinkedList mods = new LinkedList<>(); public static void loadMods() throws Exception { + Log.verbose("Start loading mods..."); // load all jars, parse the mod.json // sort the list and prioritize // load all lists and dependencies async @@ -85,6 +86,7 @@ public class ModLoader { Log.warn(String.format("An error occurred while loading %s", instance.getInfo())); } }); + Log.verbose("Loading all mods finished!"); } public static MinosoftMod loadMod(File file) { diff --git a/src/main/java/de/bixilon/minosoft/protocol/modding/channels/PluginChannelHandler.java b/src/main/java/de/bixilon/minosoft/protocol/modding/channels/PluginChannelHandler.java index 24b912eca..5a1093900 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/modding/channels/PluginChannelHandler.java +++ b/src/main/java/de/bixilon/minosoft/protocol/modding/channels/PluginChannelHandler.java @@ -22,10 +22,11 @@ import de.bixilon.minosoft.protocol.protocol.OutByteBuffer; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; public class PluginChannelHandler { - final HashMap> channels = new HashMap<>(); - final HashMap> loginChannels = new HashMap<>(); + final HashMap> channels = new HashMap<>(); + final HashMap> loginChannels = new HashMap<>(); final ArrayList registeredClientChannels = new ArrayList<>(); final ArrayList registeredServerChannels = new ArrayList<>(); final Connection connection; @@ -37,7 +38,7 @@ public class PluginChannelHandler { public void registerClientHandler(String name, ChannelHandler handler) { if (channels.get(name) == null) { // no channel with that name was registered yet - ArrayList handlerList = new ArrayList<>(); + HashSet handlerList = new HashSet<>(); handlerList.add(handler); channels.put(name, handlerList); return; @@ -50,7 +51,7 @@ public class PluginChannelHandler { public void registerLoginClientHandler(String name, LoginChannelHandler handler) { if (loginChannels.get(name) == null) { // no channel with that name was registered yet - ArrayList handlerList = new ArrayList<>(); + HashSet handlerList = new HashSet<>(); handlerList.add(handler); loginChannels.put(name, handlerList); return; diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java b/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java index cc9ea7b69..c342a3df5 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java +++ b/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java @@ -92,6 +92,7 @@ public class Connection { this.desiredVersionNumber = protocolId; Thread resolveThread = new Thread(() -> { + Minosoft.waitForStartup(); // wait until mappings are loaded if (desiredVersionNumber != -1) { setVersion(Versions.getVersionById(desiredVersionNumber)); } @@ -212,7 +213,7 @@ public class Connection { return false; } - void startHandlingThread() { + private void startHandlingThread() { handleThread = new Thread(() -> { while (isConnected()) { ClientboundPacket packet; @@ -242,7 +243,7 @@ public class Connection { return pluginChannelHandler; } - public void registerDefaultChannels() { + private void registerDefaultChannels() { // MC|Brand getPluginChannelHandler().registerClientHandler(DefaultPluginChannels.MC_BRAND.getChangeableIdentifier().get(version.getProtocolVersion()), (handler, buffer) -> { String serverVersion; diff --git a/src/main/java/de/bixilon/minosoft/util/Util.java b/src/main/java/de/bixilon/minosoft/util/Util.java index 2571a4d3e..58bf056a2 100644 --- a/src/main/java/de/bixilon/minosoft/util/Util.java +++ b/src/main/java/de/bixilon/minosoft/util/Util.java @@ -26,8 +26,8 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -202,7 +202,7 @@ public final class Util { fileOutputStream.close(); } - public static void executeInThreadPool(String name, HashSet> callables) throws InterruptedException { + public static void executeInThreadPool(String name, Collection> callables) throws InterruptedException { ExecutorService phaseLoader = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), getThreadFactory(name)); phaseLoader.invokeAll(callables); }