From 9c759294c60d90153e430af51ebbcae729d6347b Mon Sep 17 00:00:00 2001 From: cpw Date: Sat, 5 Oct 2019 17:02:54 -0400 Subject: [PATCH] Add a super early GUI for showing pre-game launch messages from FML. NOTE: this cannot show on MAC because MAC can't handle off-thread GUIs in any way shape or form, and we need the main thread to do real work. Fix forge fading in wrongly. Signed-off-by: cpw --- build.gradle | 6 +- .../net/minecraft/client/Minecraft.java.patch | 54 ++-- .../gui/screen/MainMenuScreen.java.patch | 7 +- .../net/minecraft/item/DyeColor.java.patch | 6 +- .../minecraftforge/fml/loading/FMLLoader.java | 7 +- .../fml/loading/LanguageLoadingProvider.java | 2 + .../loading/moddiscovery/ModDiscoverer.java | 4 + .../fml/loading/moddiscovery/ModFile.java | 2 + .../loading/progress/ClientVisualization.java | 264 ++++++++++++++++++ .../progress/EarlyProgressVisualization.java | 59 ++++ .../progress/StartupMessageManager.java | 108 +++++++ src/fmllauncher/resources/log4j2.xml | 6 +- .../client/ForgeHooksClient.java | 7 +- .../net/minecraftforge/common/ForgeMod.java | 1 + .../minecraftforge/fml/BrandingControl.java | 15 + .../net/minecraftforge/fml/ModLoader.java | 1 + .../fml/StartupMessageManager.java | 79 +----- .../fml/client/EarlyLoaderGUI.java | 8 +- 18 files changed, 512 insertions(+), 124 deletions(-) create mode 100644 src/fmllauncher/java/net/minecraftforge/fml/loading/progress/ClientVisualization.java create mode 100644 src/fmllauncher/java/net/minecraftforge/fml/loading/progress/EarlyProgressVisualization.java create mode 100644 src/fmllauncher/java/net/minecraftforge/fml/loading/progress/StartupMessageManager.java diff --git a/build.gradle b/build.gradle index 52a6ba9ff..808ba9842 100644 --- a/build.gradle +++ b/build.gradle @@ -402,7 +402,7 @@ project(':forge') { installer 'cpw.mods:grossjava9hacks:1.1.+' installer 'net.minecraftforge:accesstransformers:1.0.+:shadowed' installer 'net.minecraftforge:eventbus:1.0.+:service' - installer 'net.minecraftforge:forgespi:1.3.+' + installer 'net.minecraftforge:forgespi:1.4.+' installer 'net.minecraftforge:coremods:1.0.+' installer 'net.minecraftforge:unsafe:0.2.+' installer 'com.electronwill.night-config:core:3.6.0' @@ -417,6 +417,10 @@ project(':forge') { installer 'net.sf.jopt-simple:jopt-simple:5.0.4' fmllauncherImplementation 'com.google.guava:guava:21.0' fmllauncherImplementation 'com.google.code.gson:gson:2.8.0' + fmllauncherImplementation "org.lwjgl:lwjgl:3.2.2" + fmllauncherImplementation "org.lwjgl:lwjgl-glfw:3.2.2" + fmllauncherImplementation "org.lwjgl:lwjgl-opengl:3.2.2" + fmllauncherImplementation "org.lwjgl:lwjgl-stb:3.2.2" testImplementation 'org.junit.jupiter:junit-jupiter-api:5.0.0' testImplementation 'org.junit.vintage:junit-vintage-engine:5.+' testImplementation 'org.opentest4j:opentest4j:1.0.0' // needed for junit 5 diff --git a/patches/minecraft/net/minecraft/client/Minecraft.java.patch b/patches/minecraft/net/minecraft/client/Minecraft.java.patch index 24710c9a1..7da7ffc85 100644 --- a/patches/minecraft/net/minecraft/client/Minecraft.java.patch +++ b/patches/minecraft/net/minecraft/client/Minecraft.java.patch @@ -25,7 +25,15 @@ this.field_71459_aj = p_i45547_1_.field_178741_d.field_178756_a; this.field_147129_ai = func_147122_X(); this.field_71437_Z = null; -@@ -442,7 +442,6 @@ +@@ -423,6 +423,7 @@ + screensize = new ScreenSize(this.field_71474_y.field_92118_B, this.field_71474_y.field_92119_C, screensize.field_216496_c, screensize.field_216497_d, screensize.field_216498_e); + } + ++ net.minecraftforge.fml.loading.progress.EarlyProgressVisualization.INSTANCE.join(); + LongSupplier longsupplier = GLX.initGlfw(); + if (longsupplier != null) { + Util.field_211180_a = longsupplier; +@@ -442,7 +443,6 @@ this.field_195558_d.func_216526_a(this.field_71474_y.field_74350_i); this.field_71417_B = new MouseHelper(this); @@ -33,7 +41,7 @@ this.field_195559_v = new KeyboardListener(this); this.field_195559_v.func_197968_a(this.field_195558_d.func_198092_i()); GLX.init(); -@@ -451,6 +450,7 @@ +@@ -451,6 +451,7 @@ this.field_147124_at.func_147604_a(0.0F, 0.0F, 0.0F, 0.0F); this.field_110451_am = new SimpleReloadableResourceManager(ResourcePackType.CLIENT_RESOURCES, this.field_152352_aC); this.field_71474_y.func_198017_a(this.field_110448_aq); @@ -41,7 +49,7 @@ this.field_110448_aq.func_198983_a(); List list = this.field_110448_aq.func_198980_d().stream().map(ResourcePackInfo::func_195796_e).collect(Collectors.toList()); -@@ -516,12 +516,14 @@ +@@ -516,12 +517,14 @@ this.field_110451_am.func_219534_a(this.field_193995_ae); GlStateManager.viewport(0, 0, this.field_195558_d.func_198109_k(), this.field_195558_d.func_198091_l()); this.field_71452_i = new ParticleManager(this.field_71441_e, this.field_71446_o); @@ -57,7 +65,7 @@ this.field_184132_p = new DebugRenderer(this); GLX.setGlfwErrorCallback(this::func_195545_a); if (this.field_71474_y.field_74353_u && !this.field_195558_d.func_198113_j()) { -@@ -532,18 +534,20 @@ +@@ -532,18 +535,20 @@ this.field_195558_d.func_216523_b(this.field_71474_y.field_74352_v); this.field_195558_d.func_224798_d(this.field_71474_y.field_225307_E); this.field_195558_d.func_198112_b(); @@ -84,7 +92,7 @@ }, false)); } -@@ -558,7 +562,7 @@ +@@ -558,7 +563,7 @@ return Stream.of(Registry.field_212630_s.func_177774_c(p_213251_0_.func_77973_b())); }); SearchTreeReloadable searchtreereloadable = new SearchTreeReloadable<>((p_213235_0_) -> { @@ -93,7 +101,7 @@ }); NonNullList nonnulllist = NonNullList.func_191196_a(); -@@ -647,7 +651,7 @@ +@@ -647,7 +652,7 @@ Bootstrap.func_179870_a(p_71377_1_.func_71502_e()); if (p_71377_1_.func_71497_f() != null) { Bootstrap.func_179870_a("#@!@# Game crashed! Crash report saved to: #@!@# " + p_71377_1_.func_71497_f()); @@ -102,7 +110,7 @@ } else if (p_71377_1_.func_147149_a(file2)) { Bootstrap.func_179870_a("#@!@# Game crashed! Crash report saved to: #@!@# " + file2.getAbsolutePath()); System.exit(-1); -@@ -662,6 +666,7 @@ +@@ -662,6 +667,7 @@ return this.field_71474_y.field_211842_aO; } @@ -110,7 +118,7 @@ public CompletableFuture func_213237_g() { if (this.field_213276_aV != null) { return this.field_213276_aV; -@@ -741,16 +746,20 @@ +@@ -741,16 +747,20 @@ } public void func_147108_a(@Nullable Screen p_147108_1_) { @@ -135,7 +143,7 @@ if (p_147108_1_ instanceof MainMenuScreen || p_147108_1_ instanceof MultiplayerScreen) { this.field_71474_y.field_74330_P = false; this.field_71456_v.func_146158_b().func_146231_a(true); -@@ -875,11 +884,13 @@ +@@ -875,11 +885,13 @@ GlStateManager.enableTexture(); this.field_71424_I.func_76319_b(); if (!this.field_71454_w) { @@ -149,7 +157,7 @@ } this.field_71424_I.func_219897_b(); -@@ -1147,10 +1158,10 @@ +@@ -1147,10 +1159,10 @@ if (p_147115_1_ && this.field_71476_x != null && this.field_71476_x.func_216346_c() == RayTraceResult.Type.BLOCK) { BlockRayTraceResult blockraytraceresult = (BlockRayTraceResult)this.field_71476_x; BlockPos blockpos = blockraytraceresult.func_216350_a(); @@ -162,7 +170,7 @@ this.field_71439_g.func_184609_a(Hand.MAIN_HAND); } } -@@ -1177,7 +1188,7 @@ +@@ -1177,7 +1189,7 @@ case BLOCK: BlockRayTraceResult blockraytraceresult = (BlockRayTraceResult)this.field_71476_x; BlockPos blockpos = blockraytraceresult.func_216350_a(); @@ -171,7 +179,7 @@ this.field_71442_b.func_180511_b(blockpos, blockraytraceresult.func_216354_b()); break; } -@@ -1187,6 +1198,7 @@ +@@ -1187,6 +1199,7 @@ } this.field_71439_g.func_184821_cY(); @@ -179,7 +187,7 @@ } this.field_71439_g.func_184609_a(Hand.MAIN_HAND); -@@ -1236,6 +1248,9 @@ +@@ -1236,6 +1249,9 @@ } } @@ -189,7 +197,7 @@ if (!itemstack.func_190926_b() && this.field_71442_b.func_187101_a(this.field_71439_g, this.field_71441_e, hand) == ActionResultType.SUCCESS) { this.field_71460_t.field_78516_c.func_187460_a(hand); return; -@@ -1255,6 +1270,8 @@ +@@ -1255,6 +1271,8 @@ --this.field_71467_ac; } @@ -198,7 +206,7 @@ this.field_71424_I.func_76320_a("gui"); if (!this.field_71445_n) { this.field_71456_v.func_73831_a(); -@@ -1373,6 +1390,8 @@ +@@ -1373,6 +1391,8 @@ this.field_71424_I.func_219895_b("keyboard"); this.field_195559_v.func_204870_b(); this.field_71424_I.func_76319_b(); @@ -207,7 +215,7 @@ } private void func_184117_aA() { -@@ -1527,6 +1546,12 @@ +@@ -1527,6 +1547,12 @@ this.func_147108_a(worldloadprogressscreen); while(!this.field_71437_Z.func_71200_ad()) { @@ -220,7 +228,7 @@ worldloadprogressscreen.tick(); this.func_195542_b(false); -@@ -1547,11 +1572,17 @@ +@@ -1547,11 +1573,17 @@ networkmanager.func_150719_a(new ClientLoginNetHandler(networkmanager, this, (Screen)null, (p_213261_0_) -> { })); networkmanager.func_179290_a(new CHandshakePacket(socketaddress.toString(), 0, ProtocolType.LOGIN)); @@ -239,7 +247,7 @@ WorkingScreen workingscreen = new WorkingScreen(); workingscreen.func_200210_a(new TranslationTextComponent("connect.joining")); this.func_213241_c(workingscreen); -@@ -1583,10 +1614,12 @@ +@@ -1583,10 +1615,12 @@ IntegratedServer integratedserver = this.field_71437_Z; this.field_71437_Z = null; this.field_71460_t.func_190564_k(); @@ -252,7 +260,7 @@ if (integratedserver != null) { while(!integratedserver.func_213201_w()) { this.func_195542_b(false); -@@ -1624,6 +1657,7 @@ +@@ -1624,6 +1658,7 @@ } TileEntityRendererDispatcher.field_147556_a.func_147543_a(p_213257_1_); @@ -260,7 +268,7 @@ } public final boolean func_71355_q() { -@@ -1649,112 +1683,8 @@ +@@ -1649,112 +1684,8 @@ private void func_147112_ai() { if (this.field_71476_x != null && this.field_71476_x.func_216346_c() != RayTraceResult.Type.MISS) { @@ -375,7 +383,7 @@ } } -@@ -1826,6 +1756,7 @@ +@@ -1826,6 +1757,7 @@ return field_71432_P; } @@ -383,7 +391,7 @@ public CompletableFuture func_213245_w() { return this.func_213169_a(this::func_213237_g).thenCompose((p_213240_0_) -> { return p_213240_0_; -@@ -1972,6 +1903,8 @@ +@@ -1972,6 +1904,8 @@ } public MusicTicker.MusicType func_147109_W() { @@ -392,7 +400,7 @@ if (this.field_71462_r instanceof WinGameScreen) { return MusicTicker.MusicType.CREDITS; } else if (this.field_71439_g == null) { -@@ -2128,4 +2061,12 @@ +@@ -2128,4 +2062,12 @@ public LoadingGui func_213250_au() { return this.field_213279_p; } diff --git a/patches/minecraft/net/minecraft/client/gui/screen/MainMenuScreen.java.patch b/patches/minecraft/net/minecraft/client/gui/screen/MainMenuScreen.java.patch index 1816597ec..498ca7e74 100644 --- a/patches/minecraft/net/minecraft/client/gui/screen/MainMenuScreen.java.patch +++ b/patches/minecraft/net/minecraft/client/gui/screen/MainMenuScreen.java.patch @@ -48,7 +48,7 @@ if (this.field_73975_c != null) { GlStateManager.pushMatrix(); GlStateManager.translatef((float)(this.width / 2 + 90), 70.0F, 0.0F); -@@ -222,7 +229,10 @@ +@@ -222,7 +229,13 @@ s = s + ("release".equalsIgnoreCase(this.minecraft.func_184123_d()) ? "" : "/" + this.minecraft.func_184123_d()); } @@ -57,10 +57,13 @@ + this.drawString(this.font, brd, 2, this.height - ( 10 + brdline * (this.font.field_78288_b + 1)), 16777215 | l) + ); + ++ net.minecraftforge.fml.BrandingControl.forEachAboveCopyrightLine((brdline, brd) -> ++ this.drawString(this.font, brd, this.width - font.func_78256_a(brd), this.height - (10 + (brdline + 1) * ( this.font.field_78288_b + 1)), 16777215 | l) ++ ); this.drawString(this.font, "Copyright Mojang AB. Do not distribute!", this.field_193979_N, this.height - 10, 16777215 | l); if (p_render_1_ > this.field_193979_N && p_render_1_ < this.field_193979_N + this.field_193978_M && p_render_2_ > this.height - 10 && p_render_2_ < this.height) { fill(this.field_193979_N, this.height - 1, this.field_193979_N + this.field_193978_M, this.height, 16777215 | l); -@@ -240,6 +250,7 @@ +@@ -240,6 +253,7 @@ if (this.func_183501_a() && f1 >= 1.0F) { this.field_183503_M.render(p_render_1_, p_render_2_, p_render_3_); } diff --git a/patches/minecraft/net/minecraft/item/DyeColor.java.patch b/patches/minecraft/net/minecraft/item/DyeColor.java.patch index f4af7fa8d..2de3c4d96 100644 --- a/patches/minecraft/net/minecraft/item/DyeColor.java.patch +++ b/patches/minecraft/net/minecraft/item/DyeColor.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/item/DyeColor.java +++ b/net/minecraft/item/DyeColor.java -@@ -43,6 +43,7 @@ +@@ -41,6 +41,7 @@ private final int field_196066_w; private final float[] field_193352_x; private final int field_196067_y; @@ -8,7 +8,7 @@ private final int field_218390_z; private DyeColor(int p_i50049_3_, String p_i50049_4_, int p_i50049_5_, MaterialColor p_i50049_6_, int p_i50049_7_, int p_i50049_8_) { -@@ -55,6 +56,7 @@ +@@ -53,6 +54,7 @@ int j = (p_i50049_5_ & '\uff00') >> 8; int k = (p_i50049_5_ & 255) >> 0; this.field_196066_w = k << 16 | j << 8 | i << 0; @@ -16,7 +16,7 @@ this.field_193352_x = new float[]{(float)i / 255.0F, (float)j / 255.0F, (float)k / 255.0F}; this.field_196067_y = p_i50049_7_; } -@@ -120,4 +122,25 @@ +@@ -115,4 +117,25 @@ public String func_176610_l() { return this.field_176785_v; } diff --git a/src/fmllauncher/java/net/minecraftforge/fml/loading/FMLLoader.java b/src/fmllauncher/java/net/minecraftforge/fml/loading/FMLLoader.java index 70d3b998a..ec8d9fa98 100644 --- a/src/fmllauncher/java/net/minecraftforge/fml/loading/FMLLoader.java +++ b/src/fmllauncher/java/net/minecraftforge/fml/loading/FMLLoader.java @@ -34,6 +34,8 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler; import net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer; import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.fml.loading.progress.EarlyProgressVisualization; +import net.minecraftforge.fml.loading.progress.StartupMessageManager; import net.minecraftforge.forgespi.Environment; import net.minecraftforge.forgespi.coremod.ICoreModProvider; import net.minecraftforge.forgespi.locating.IModFile; @@ -176,6 +178,8 @@ public class FMLLoader FMLCommonLaunchHandler commonLaunchHandler = (FMLCommonLaunchHandler)launchHandler.get(); naming = commonLaunchHandler.getNaming(); dist = commonLaunchHandler.getDist(); + EarlyProgressVisualization.INSTANCE.accept(dist); + StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Early Loading!")); accessTransformer.getExtension().accept(Pair.of(naming, "srg")); mcVersion = (String) arguments.get("mcVersion"); @@ -232,13 +236,12 @@ public class FMLLoader public static void beforeStart(ITransformingClassLoader launchClassLoader) { FMLLoader.launchClassLoader = (TransformingClassLoader) launchClassLoader.getInstance(); + StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Launching minecraft")); } - public static LoadingModList getLoadingModList() { return loadingModList; - } public static TransformingClassLoader getLaunchClassLoader() diff --git a/src/fmllauncher/java/net/minecraftforge/fml/loading/LanguageLoadingProvider.java b/src/fmllauncher/java/net/minecraftforge/fml/loading/LanguageLoadingProvider.java index dc856f51d..3af87f0ee 100644 --- a/src/fmllauncher/java/net/minecraftforge/fml/loading/LanguageLoadingProvider.java +++ b/src/fmllauncher/java/net/minecraftforge/fml/loading/LanguageLoadingProvider.java @@ -20,6 +20,7 @@ package net.minecraftforge.fml.loading; import cpw.mods.modlauncher.ServiceLoaderStreamUtils; +import net.minecraftforge.fml.loading.progress.StartupMessageManager; import net.minecraftforge.forgespi.language.IModLanguageProvider; import net.minecraftforge.fml.loading.moddiscovery.ExplodedDirectoryLocator; import net.minecraftforge.fml.loading.moddiscovery.ModFile; @@ -127,6 +128,7 @@ public class LanguageLoadingProvider throw new RuntimeException("Failed to find implementation version for language provider "+ lp.name()); } LOGGER.debug(CORE, "Found language provider {}, version {}", lp.name(), impl); + StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Loaded language provider "+lp.name()+ " " + impl)); languageProviderMap.put(lp.name(), new ModLanguageWrapper(lp, new DefaultArtifactVersion(impl))); }); } diff --git a/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModDiscoverer.java b/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModDiscoverer.java index bd5452a04..92a18b7ce 100644 --- a/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModDiscoverer.java +++ b/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModDiscoverer.java @@ -24,6 +24,7 @@ import cpw.mods.modlauncher.ServiceLoaderStreamUtils; import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.LoadingModList; import net.minecraftforge.fml.loading.ModSorter; +import net.minecraftforge.fml.loading.progress.StartupMessageManager; import net.minecraftforge.forgespi.Environment; import net.minecraftforge.forgespi.locating.IModFile; import net.minecraftforge.forgespi.locating.IModLocator; @@ -66,6 +67,7 @@ public class ModDiscoverer { public ModDiscoverer(Map arguments) { Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODFOLDERFACTORY.get(), v->ModsFolderLocator::new); + Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v->StartupMessageManager.locatorConsumer().orElseGet(()->s->{})); locators = ServiceLoader.load(IModLocator.class); locatorList = ServiceLoaderStreamUtils.toList(this.locators); locatorList.forEach(l->l.initArguments(arguments)); @@ -85,6 +87,7 @@ public class ModDiscoverer { .map(IModLocator::scanMods) .flatMap(Collection::stream) .peek(mf -> LOGGER.debug(SCAN,"Found mod file {} of type {} with locator {}", mf.getFileName(), mf.getType(), mf.getLocator())) + .peek(mf -> StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found mod file "+mf.getFileName()+" of type "+mf.getType()))) .map(ModFile.class::cast) .collect(Collectors.groupingBy(IModFile::getType)); @@ -102,6 +105,7 @@ public class ModDiscoverer { } } LOGGER.debug(SCAN,"Found {} mod files with {} mods", mods::size, ()->mods.stream().mapToInt(mf -> mf.getModInfos().size()).sum()); + StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found "+mods.size()+" modfiles to load")); final LoadingModList loadingModList = ModSorter.sort(mods); loadingModList.addCoreMods(); loadingModList.addAccessTransformers(); diff --git a/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java b/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java index e1a925e1e..defe1005e 100644 --- a/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java +++ b/src/fmllauncher/java/net/minecraftforge/fml/loading/moddiscovery/ModFile.java @@ -20,6 +20,7 @@ package net.minecraftforge.fml.loading.moddiscovery; import com.google.common.collect.ImmutableMap; +import net.minecraftforge.fml.loading.progress.StartupMessageManager; import net.minecraftforge.forgespi.language.IModFileInfo; import net.minecraftforge.forgespi.language.IModInfo; import net.minecraftforge.forgespi.language.ModFileScanData; @@ -178,6 +179,7 @@ public class ModFile implements IModFile { if (throwable != null) { this.scanError = throwable; } + StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Completed deep scan of "+this.getFileName())); } @Override diff --git a/src/fmllauncher/java/net/minecraftforge/fml/loading/progress/ClientVisualization.java b/src/fmllauncher/java/net/minecraftforge/fml/loading/progress/ClientVisualization.java new file mode 100644 index 000000000..ca341c2bb --- /dev/null +++ b/src/fmllauncher/java/net/minecraftforge/fml/loading/progress/ClientVisualization.java @@ -0,0 +1,264 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.fml.loading.progress; + +import org.apache.commons.lang3.tuple.Pair; +import org.lwjgl.glfw.GLFWErrorCallback; +import org.lwjgl.glfw.GLFWVidMode; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL14; +import org.lwjgl.stb.STBEasyFont; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryUsage; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.List; + +import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks; +import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.glfw.GLFW.glfwCreateWindow; +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.system.MemoryUtil.NULL; + +class ClientVisualization implements EarlyProgressVisualization.Visualization { + private final int screenWidth = 800; + private final int screenHeight = 400; + private long window; + private Thread thread; + private boolean running; + + private void initWindow() { + GLFWErrorCallback.createPrint(System.err).set(); + + if (!glfwInit()) { + throw new IllegalStateException("Unable to initialize GLFW"); + } + + glfwDefaultWindowHints(); + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(screenWidth, screenHeight, "FML early loading progress", NULL, NULL); + if (window == NULL) { + throw new RuntimeException("Failed to create the GLFW window"); // ignore it and make the GUI optional? + } + + try (MemoryStack stack = stackPush()) { + IntBuffer pWidth = stack.mallocInt(1); + IntBuffer pHeight = stack.mallocInt(1); + IntBuffer monPosLeft = stack.mallocInt(1); + IntBuffer monPosTop = stack.mallocInt(1); + glfwGetWindowSize(window, pWidth, pHeight); + GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + glfwGetMonitorPos(glfwGetPrimaryMonitor(), monPosLeft, monPosTop); + // Center the window + glfwSetWindowPos( + window, + (vidmode.width() - pWidth.get(0)) / 2 + monPosLeft.get(0), + (vidmode.height() - pHeight.get(0)) / 2 + monPosTop.get(0) + ); + } + glfwMakeContextCurrent(window); + glfwSwapInterval(1); + glfwShowWindow(window); + GL.createCapabilities(); + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + + private void renderProgress() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0D, screenWidth, screenHeight, 0.0D, -1000.0D, 1000.0D); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + +// // replace with more modern opengl? +// glBegin(GL_QUADS); +// glColor3f(0.1f, 0.1f, 0.9f); +// glVertex2f(0, 0); +// glVertex2f(0, screenHeight); +// glVertex2f(screenWidth * progress, screenHeight); +// glVertex2f(screenWidth * progress, 0); +// glEnd(); + + glEnableClientState(GL11.GL_VERTEX_ARRAY); + glEnable(GL_BLEND); + renderMessages(); + glfwSwapBuffers(window); + glfwPollEvents(); + } + + private static float clamp(float num, float min, float max) { + if (num < min) { + return min; + } else { + return num > max ? max : num; + } + } + + private static int clamp(int num, int min, int max) { + if (num < min) { + return min; + } else { + return num > max ? max : num; + } + } + + private static int hsvToRGB(float hue, float saturation, float value) { + int i = (int)(hue * 6.0F) % 6; + float f = hue * 6.0F - (float)i; + float f1 = value * (1.0F - saturation); + float f2 = value * (1.0F - f * saturation); + float f3 = value * (1.0F - (1.0F - f) * saturation); + float f4; + float f5; + float f6; + switch(i) { + case 0: + f4 = value; + f5 = f3; + f6 = f1; + break; + case 1: + f4 = f2; + f5 = value; + f6 = f1; + break; + case 2: + f4 = f1; + f5 = value; + f6 = f3; + break; + case 3: + f4 = f1; + f5 = f2; + f6 = value; + break; + case 4: + f4 = f3; + f5 = f1; + f6 = value; + break; + case 5: + f4 = value; + f5 = f1; + f6 = f2; + break; + default: + throw new RuntimeException("Something went wrong when converting from HSV to RGB. Input was " + hue + ", " + saturation + ", " + value); + } + + int j = clamp((int)(f4 * 255.0F), 0, 255); + int k = clamp((int)(f5 * 255.0F), 0, 255); + int l = clamp((int)(f6 * 255.0F), 0, 255); + return j << 16 | k << 8 | l; + } + + private void renderMessages() { + List> messages = StartupMessageManager.getMessages(); + for (int i = 0; i < messages.size(); i++) { + final Pair pair = messages.get(i); + final float fade = clamp((4000.0f - (float) pair.getLeft() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f); + if (fade <0.01f) continue; + StartupMessageManager.Message msg = pair.getRight(); + renderMessage(msg.getText(), msg.getTypeColour(), ((screenHeight - 15) / 20) - i, fade); + } + renderMemoryInfo(); + } + + private static final float[] memorycolour = new float[] { 0.0f, 0.0f, 0.0f}; + + private void renderMemoryInfo() { + final MemoryUsage heapusage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); + final MemoryUsage offheapusage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage(); + final float pctmemory = (float) heapusage.getUsed() / heapusage.getMax(); + String memory = String.format("Memory Heap: %d / %d MB (%.1f%%) OffHeap: %d MB", heapusage.getUsed() >> 20, heapusage.getMax() >> 20, pctmemory * 100.0, offheapusage.getUsed() >> 20); + + final int i = hsvToRGB((1.0f - (float)Math.pow(pctmemory, 1.5f)) / 3f, 1.0f, 0.5f); + memorycolour[2] = ((i) & 0xFF) / 255.0f; + memorycolour[1] = ((i >> 8 ) & 0xFF) / 255.0f; + memorycolour[0] = ((i >> 16 ) & 0xFF) / 255.0f; + renderMessage(memory, memorycolour, 1, 1.0f); + } + + private void renderMessage(final String message, final float[] colour, int row, float alpha) { + ByteBuffer charBuffer = MemoryUtil.memAlloc(message.length() * 270); + int quads = STBEasyFont.stb_easy_font_print(0, 0, message, null, charBuffer); + + glVertexPointer(3, GL11.GL_FLOAT, 16, charBuffer); + glEnable(GL_BLEND); + GL14.glBlendColor(0,0,0, alpha); + glBlendFunc(GL14.GL_CONSTANT_ALPHA, GL14.GL_ONE_MINUS_CONSTANT_ALPHA); + glColor3f(colour[0], colour[1], colour[2]); + glPushMatrix(); + glTranslatef(10, row * 20, 0); + glScalef(2, 2, 1); + glDrawArrays(GL11.GL_QUADS, 0, quads * 4); + glPopMatrix(); + MemoryUtil.memFree(charBuffer); + } + + private void closeWindow() { + glfwFreeCallbacks(window); + glfwDestroyWindow(window); + glfwTerminate(); + glfwSetErrorCallback(null).free(); + } + + @Override + public void start() { + thread = new Thread(this::run); + thread.setDaemon(true); + thread.start(); + } + + private void run() { + initWindow(); + running = true; + while (running) { + renderProgress(); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + } + closeWindow(); + } + + @Override + public void join() { +// try { +// Thread.sleep(10000); +// } catch (InterruptedException e) { +// } + running = false; + try { + thread.join(); + } catch (InterruptedException e) { + } + } +} diff --git a/src/fmllauncher/java/net/minecraftforge/fml/loading/progress/EarlyProgressVisualization.java b/src/fmllauncher/java/net/minecraftforge/fml/loading/progress/EarlyProgressVisualization.java new file mode 100644 index 000000000..cee17fe2b --- /dev/null +++ b/src/fmllauncher/java/net/minecraftforge/fml/loading/progress/EarlyProgressVisualization.java @@ -0,0 +1,59 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.fml.loading.progress; + +import net.minecraftforge.api.distmarker.Dist; + +import java.util.Locale; + +public enum EarlyProgressVisualization { + INSTANCE; + + private Visualization visualization; + + public void accept(final Dist dist) { + if (visualization != null) return; + // We don't show the window on Mac because mac is super mega triple shit and can't handle anything out of the ordinary + final boolean ismac = System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("mac"); + visualization = dist.isClient() && !ismac && Boolean.parseBoolean(System.getProperty("fml.earlyprogresswindow", "true")) ? new ClientVisualization() : new NoVisualization(); + visualization.start(); + } + + public void join() { + visualization.join(); + } + + interface Visualization { + void start(); + void join(); + } + + private static class NoVisualization implements Visualization { + @Override + public void start() { + } + + @Override + public void join() { + } + } + +} + diff --git a/src/fmllauncher/java/net/minecraftforge/fml/loading/progress/StartupMessageManager.java b/src/fmllauncher/java/net/minecraftforge/fml/loading/progress/StartupMessageManager.java new file mode 100644 index 000000000..dfbebb4a0 --- /dev/null +++ b/src/fmllauncher/java/net/minecraftforge/fml/loading/progress/StartupMessageManager.java @@ -0,0 +1,108 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.fml.loading.progress; + +import com.google.common.base.Ascii; +import com.google.common.base.CharMatcher; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class StartupMessageManager { + private static final EnumMap> messages = new EnumMap<>(MessageType.class); + static { + Arrays.stream(MessageType.values()).forEach(mt->messages.computeIfAbsent(mt, k->new CopyOnWriteArrayList<>())); + } + + public static List> getMessages() { + final long ts = System.nanoTime(); + return messages.values().stream().flatMap(Collection::stream). + sorted(Comparator.comparingLong(Message::getTimestamp).thenComparing(Message::getText).reversed()). + map(m -> Pair.of((int) ((ts - m.timestamp) / 1e6), m)). + collect(Collectors.toList()); + } + + public static class Message { + private final String text; + private final MessageType type; + private final long timestamp; + + public Message(final String text, final MessageType type) { + this.text = text; + this.type = type; + this.timestamp = System.nanoTime(); + } + + public String getText() { + return text; + } + + MessageType getType() { + return type; + } + + long getTimestamp() { + return timestamp; + } + + public float[] getTypeColour() { + return type.colour(); + } + } + + enum MessageType { + MC(0.0f, 0.0f, 0.0f), + ML(0.0f, 0.0f, 0.5f), + LOC(0.0f, 0.5f, 0.0f), + MOD(0.5f, 0.0f, 0.0f); + + private final float[] colour; + + MessageType(final float r, final float g, final float b) { + colour = new float[] {r,g,b}; + } + + public float[] colour() { + return colour; + } + } + + public static void addModMessage(final String message) { + final String safeMessage = Ascii.truncate(CharMatcher.ascii().retainFrom(message),80,"~"); + final List messages = StartupMessageManager.messages.get(MessageType.MOD); + messages.subList(0, Math.max(0, messages.size() - 20)).clear(); + messages.add(new Message(safeMessage, MessageType.MOD)); + } + + public static Optional> modLoaderConsumer() { + return Optional.of(s-> messages.get(MessageType.ML).add(new Message(s, MessageType.ML))); + } + + public static Optional> locatorConsumer() { + return Optional.of(s -> messages.get(MessageType.LOC).add(new Message(s, MessageType.LOC))); + } + + static Optional> mcLoaderConsumer() { + return Optional.of(s-> messages.get(MessageType.MC).add(new Message(s, MessageType.MC))); + } +} diff --git a/src/fmllauncher/resources/log4j2.xml b/src/fmllauncher/resources/log4j2.xml index e1a5e9ee2..a4988b7a8 100644 --- a/src/fmllauncher/resources/log4j2.xml +++ b/src/fmllauncher/resources/log4j2.xml @@ -20,10 +20,10 @@ - + - - + + diff --git a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java index 9a998a6a2..77304e431 100644 --- a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java +++ b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java @@ -320,13 +320,10 @@ public class ForgeHooksClient default: break; } - if (line != null) - { - // if we have a line, render it in the bottom right, above Mojang's copyright line - gui.drawString(font, line, width - font.getStringWidth(line) - 2, height - (2 * (font.FONT_HEIGHT + 1)), -1); - } + forgeStatusLine = line; } + public static String forgeStatusLine; public static ISound playSound(SoundEngine manager, ISound sound) { PlaySoundEvent e = new PlaySoundEvent(manager, sound); diff --git a/src/main/java/net/minecraftforge/common/ForgeMod.java b/src/main/java/net/minecraftforge/common/ForgeMod.java index 4050224b2..be881027f 100644 --- a/src/main/java/net/minecraftforge/common/ForgeMod.java +++ b/src/main/java/net/minecraftforge/common/ForgeMod.java @@ -28,6 +28,7 @@ import net.minecraftforge.fml.event.lifecycle.FMLModIdMappingEvent; import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.event.server.FMLServerStoppingEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fml.loading.progress.StartupMessageManager; import net.minecraftforge.server.command.ConfigCommand; import net.minecraftforge.server.command.ForgeCommand; import net.minecraftforge.versions.forge.ForgeVersion; diff --git a/src/main/java/net/minecraftforge/fml/BrandingControl.java b/src/main/java/net/minecraftforge/fml/BrandingControl.java index 083a4a2ad..e58bd2656 100644 --- a/src/main/java/net/minecraftforge/fml/BrandingControl.java +++ b/src/main/java/net/minecraftforge/fml/BrandingControl.java @@ -27,6 +27,7 @@ import java.util.stream.IntStream; import net.minecraft.resources.IResourceManager; import net.minecraft.resources.IResourceManagerReloadListener; +import net.minecraftforge.client.ForgeHooksClient; import net.minecraftforge.versions.forge.ForgeVersion; import net.minecraftforge.versions.mcp.MCPVersion; @@ -35,6 +36,7 @@ public class BrandingControl { private static List brandings; private static List brandingsNoMC; + private static List overCopyrightBrandings; private static void computeBranding() { @@ -61,11 +63,24 @@ public class BrandingControl } } + private static void computeOverCopyrightBrandings() { + if (overCopyrightBrandings == null) { + ImmutableList.Builder brd = ImmutableList.builder(); + if (ForgeHooksClient.forgeStatusLine != null) brd.add(ForgeHooksClient.forgeStatusLine); + overCopyrightBrandings = brd.build(); + } + } + public static void forEachLine(boolean includeMC, boolean reverse, BiConsumer lineConsumer) { final List brandings = getBrandings(includeMC, reverse); IntStream.range(0, brandings.size()).boxed().forEachOrdered(idx -> lineConsumer.accept(idx, brandings.get(idx))); } + public static void forEachAboveCopyrightLine(BiConsumer lineConsumer) { + computeOverCopyrightBrandings(); + IntStream.range(0, overCopyrightBrandings.size()).boxed().forEachOrdered(idx->lineConsumer.accept(idx, overCopyrightBrandings.get(idx))); + } + public static String getClientBranding() { return "forge"; } diff --git a/src/main/java/net/minecraftforge/fml/ModLoader.java b/src/main/java/net/minecraftforge/fml/ModLoader.java index c22a27e42..812d8b23d 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoader.java +++ b/src/main/java/net/minecraftforge/fml/ModLoader.java @@ -35,6 +35,7 @@ import net.minecraftforge.fml.loading.LoadingModList; import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier; import net.minecraftforge.fml.loading.moddiscovery.ModFile; import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; +import net.minecraftforge.fml.loading.progress.StartupMessageManager; import net.minecraftforge.fml.network.FMLNetworkConstants; import net.minecraftforge.fml.network.NetworkRegistry; import net.minecraftforge.forgespi.language.IModInfo; diff --git a/src/main/java/net/minecraftforge/fml/StartupMessageManager.java b/src/main/java/net/minecraftforge/fml/StartupMessageManager.java index b409cf773..0a6713128 100644 --- a/src/main/java/net/minecraftforge/fml/StartupMessageManager.java +++ b/src/main/java/net/minecraftforge/fml/StartupMessageManager.java @@ -19,85 +19,8 @@ package net.minecraftforge.fml; -import com.google.common.base.Ascii; -import com.google.common.base.CharMatcher; -import org.apache.commons.lang3.tuple.Pair; - -import java.util.*; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Consumer; -import java.util.stream.Collectors; - public class StartupMessageManager { - private static final EnumMap> messages = new EnumMap<>(MessageType.class); - static { - Arrays.stream(MessageType.values()).forEach(mt->messages.computeIfAbsent(mt, k->new CopyOnWriteArrayList<>())); - } - - public static List> getMessages() { - final long ts = System.nanoTime(); - return messages.values().stream().flatMap(Collection::stream). - sorted(Comparator.comparingLong(Message::getTimestamp).thenComparing(Message::getText).reversed()). - map(m -> Pair.of((int) ((ts - m.timestamp) / 1e6), m)). - collect(Collectors.toList()); - } - - public static class Message { - private final String text; - private final MessageType type; - private final long timestamp; - - public Message(final String text, final MessageType type) { - this.text = text; - this.type = type; - this.timestamp = System.nanoTime(); - } - - public String getText() { - return text; - } - - MessageType getType() { - return type; - } - - long getTimestamp() { - return timestamp; - } - - public float[] getTypeColour() { - return type.colour(); - } - } - - enum MessageType { - MC(0.0f, 0.0f, 0.0f), - ML(0.0f, 0.0f, 0.5f), - MOD(0.5f, 0.0f, 0.0f); - - private final float[] colour; - - MessageType(final float r, final float g, final float b) { - colour = new float[] {r,g,b}; - } - - public float[] colour() { - return colour; - } - } - public static void addModMessage(final String message) { - final String safeMessage = Ascii.truncate(CharMatcher.ascii().retainFrom(message),80,"~"); - final List messages = StartupMessageManager.messages.get(MessageType.MOD); - messages.subList(0, Math.max(0, messages.size() - 20)).clear(); - messages.add(new Message(safeMessage, MessageType.MOD)); - } - - static Optional> modLoaderConsumer() { - return Optional.of(s-> messages.get(MessageType.ML).add(new Message(s, MessageType.ML))); - } - - static Optional> mcLoaderConsumer() { - return Optional.of(s-> messages.get(MessageType.MC).add(new Message(s, MessageType.MC))); + net.minecraftforge.fml.loading.progress.StartupMessageManager.addModMessage(message); } } diff --git a/src/main/java/net/minecraftforge/fml/client/EarlyLoaderGUI.java b/src/main/java/net/minecraftforge/fml/client/EarlyLoaderGUI.java index 7a322ac38..43fa83158 100644 --- a/src/main/java/net/minecraftforge/fml/client/EarlyLoaderGUI.java +++ b/src/main/java/net/minecraftforge/fml/client/EarlyLoaderGUI.java @@ -19,27 +19,21 @@ package net.minecraftforge.fml.client; -import com.google.common.base.Ascii; -import com.google.common.base.CharMatcher; import com.mojang.blaze3d.platform.GlStateManager; import net.minecraft.client.MainWindow; import net.minecraft.client.Minecraft; import net.minecraft.util.math.MathHelper; -import net.minecraftforge.fml.StartupMessageManager; +import net.minecraftforge.fml.loading.progress.StartupMessageManager; import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.tuple.Triple; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL14; import org.lwjgl.stb.STBEasyFont; -import org.lwjgl.stb.STBImage; import org.lwjgl.system.MemoryUtil; import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; import java.lang.management.MemoryUsage; import java.nio.ByteBuffer; import java.util.List; -import java.util.function.Consumer; public class EarlyLoaderGUI { private final MainWindow window;