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 two points: once * during startup, and once per frame. Everything a mod does hangs off those two * 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 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 (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)); } } /** * Runs scheduled mod work and draws registered overlays. * * Called once per frame with the main framebuffer bound and the game's own * matrices already unwound, so the overlay sets up its own projection and * leaves the state it did not set alone. */ public static void renderFrame(Minecraft minecraft) { if (loader == null || surface == null || !surface.available()) { return; } if (frameFailed) { return; } if (client != null) { client.advanceFrame(); } try { loader.tick(); if (client != null) { client.advanceTick(); } 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); } } }