package net.rustnotmc.client; import java.io.File; import java.util.List; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.renderer.GlStateManager; import net.rustnotmc.api.client.ClientHost; import net.rustnotmc.api.hud.HudLayout; import net.rustnotmc.loader.Log; import net.rustnotmc.loader.ModContainer; import net.rustnotmc.loader.ModLoader; /** * Where the client and the mod loader meet. * * The loader is a library, not an agent. The game calls in at three points: * once during startup, once per game tick and once per frame. Everything a mod * does hangs off those three calls, so the loader never has to know how a game * loop is shaped. * * Every entry point is wrapped. A mod that throws must not be able to take the * client down with it, and a loader that cannot start must not stop the game * from being a game. * * This file lives in game-fork/src-overlay because src is regenerated from the * vanilla jar on every build and anything written there is lost. */ public final class RsxBootstrap { private static ModLoader loader; private static RsxHudSurface surface; private static RsxClientServices client; private static boolean broken; private static boolean frameFailed; private static boolean tickFailed; private RsxBootstrap() { } /** True once mods have been discovered and enabled. */ public static boolean ready() { return loader != null && !broken; } public static ModLoader loader() { return loader; } public static int loadedMods() { return loader == null ? 0 : loader.count(); } /** * Discovers and enables mods. Called once, after the client has a display, * a font and a resource manager, so a mod may touch any of them. */ public static synchronized void start(Minecraft minecraft) { if (loader != null || broken) { return; } try { // the client API goes up before any mod runs, so onConstruct can use // it: a mod registers commands and reads the screen there client = new RsxClientServices(minecraft); ClientHost.install(client); File directory = new File(minecraft.gameDir, "mods"); loader = new ModLoader(directory); int found = loader.discover(); Log.info("RustNotMC: scanning " + directory + ", " + found + " mod jar(s)"); if (!loader.resolve()) { reportProblems(); broken = true; loader = null; return; } loader.loadAll(); surface = new RsxHudSurface(minecraft); reportProblems(); if (loader.count() == 0) { Log.info("RustNotMC: no mods loaded"); return; } StringBuilder line = new StringBuilder("RustNotMC: loaded "); List mods = loader.mods(); for (int i = 0; i < mods.size(); i++) { if (i > 0) { line.append(", "); } ModContainer mod = mods.get(i); line.append(mod.id()).append(' ').append(mod.version()); } Log.info(line.toString()); } catch (Throwable throwable) { broken = true; loader = null; Log.error("RustNotMC: mod loader failed to start", throwable); } } /** * Offers a typed chat line to the mod commands. * * Called from the point where a line leaves the client. Returns true when a * CLIENT scoped command carried it out and the line must not be sent on. * SERVER and BOTH commands return false on purpose: the server is the one * that has to see them and decide. */ public static boolean handleChatCommand(String line) { if (line == null || !line.startsWith("/")) { return false; } // our own commands come first: they are not mod commands, and a mod // should not be able to take a name that the game itself uses if (RsxHitboxDebug.command(line)) { return true; } if (client == null || !ClientHost.present()) { return false; } return client.dispatchCommand(line, localName()); } private static String localName() { Minecraft mc = Minecraft.getMinecraft(); if (mc != null && mc.getSession() != null) { return mc.getSession().getUsername(); } return "local"; } private static void reportProblems() { List problems = loader.problems(); for (int i = 0; i < problems.size(); i++) { Log.error("RustNotMC: " + problems.get(i)); } } /** * Writes down what OpenGL we ended up with. * * The far view is written against a modern context, and the first thing to * ask when it draws nothing is what the driver actually gave us. */ public static void logGl() { try { Log.info("OpenGL " + org.lwjgl.opengl.GL11.glGetString( org.lwjgl.opengl.GL11.GL_VERSION) + " | " + org.lwjgl.opengl.GL11.glGetString( org.lwjgl.opengl.GL11.GL_RENDERER) + " | " + org.lwjgl.opengl.GL11.glGetString( org.lwjgl.opengl.GL11.GL_VENDOR)); // The game's own FXAA shader asks for an extension that modern // drivers no longer hand out. It is worth knowing whether it is // there before a player turns the effect on and the game falls // over. String extensions = org.lwjgl.opengl.GL11.glGetString( org.lwjgl.opengl.GL11.GL_EXTENSIONS); Log.info("OpenGL EXT_gpu_shader4: " + (extensions != null && extensions.contains("GL_EXT_gpu_shader4"))); } catch (Throwable throwable) { Log.error("RustNotMC: could not read the OpenGL version", throwable); } } /** * Runs one game tick of mod work. * * Called from inside the client's tick loop, next to its own tick, so a mod * sees the same twenty steps per second the game does. Driving this from * the frame loop instead would hand a mod as many ticks as the machine can * draw: at 200 fps a mod would run its timers seven times too fast, and any * mod that counts ticks would disagree with the world it is drawn over. */ public static synchronized void clientTick() { // the connection is pumped from here, not from the screen that started // it. A handshake that only runs while a screen is up is a handshake // that the player can break by moving the window. net.rustnotmc.client.net.RsxNetworkManager.pump(); if (loader == null || broken || tickFailed) { return; } try { loader.tick(); if (client != null) { client.advanceTick(); } } catch (Throwable throwable) { tickFailed = true; Log.error("RustNotMC: mod tick failed, disabling mod ticks", throwable); } } /** * Counts the frame and draws registered overlays. * * Called once per frame after the game has presented its own framebuffer and * unwound its matrices, so the overlay sets up its own projection and leaves * the state it did not set alone. */ public static void renderFrame(Minecraft minecraft) { RsxFrameStats.frame(); if (client != null) { client.advanceFrame(); } RsxHitboxDebug.draw(minecraft); drawOverlay(minecraft); } /** * Draws the overlays on top of the world loading screen. * * The loading screen is the one place that does not go through the frame * tail. While terrain is generated it draws into a framebuffer of its own, * blits that to the window and presents it itself, up to ten times a second, * straight from the thread doing the generating. Anything drawn from the * frame hook is wiped by the next of those presents, which is why an overlay * used to vanish for the whole load and come back afterwards in the world. */ public static void renderLoadingScreen(Minecraft minecraft) { drawOverlay(minecraft); } private static void drawOverlay(Minecraft minecraft) { if (loader == null || surface == null || !surface.available()) { return; } if (frameFailed) { return; } try { ScaledResolution scaled = new ScaledResolution(minecraft); int width = scaled.getScaledWidth(); int height = scaled.getScaledHeight(); if (width <= 0 || height <= 0) { return; } // the overlay draws in flat screen space, so depth would fight it // and the blend state has to be ours for the duration GlStateManager.disableDepth(); GlStateManager.pushMatrix(); GlStateManager.matrixMode(5889); GlStateManager.loadIdentity(); GlStateManager.ortho(0.0D, width, height, 0.0D, 1000.0D, 3000.0D); GlStateManager.matrixMode(5888); GlStateManager.loadIdentity(); GlStateManager.translate(0.0F, 0.0F, -2000.0F); GlStateManager.enableBlend(); GlStateManager.tryBlendFuncSeparate( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); try { HudLayout.render(surface); } finally { GlStateManager.disableBlend(); GlStateManager.enableDepth(); GlStateManager.popMatrix(); } } catch (Throwable throwable) { // one bad frame is a bug worth seeing, but it must not become a // per-frame stack trace or a dead client frameFailed = true; Log.error("RustNotMC: overlay rendering failed, disabling overlays", throwable); } } }