9980c1561d
- RsxPlayerTuning: высота при подкрадывании 1.45 (ванилла 1.65), прыжок с места 1.5, в движении ванильные 1.25, высота глаз в шифте 1.37. - RsxPlayer: прыжок по наличию горизонтального ввода, глаза при подкрадывании. - Патч в EntityPlayer.updateSize: высота при подкрадывании из нашего класса. - Импульс прыжка считается обратным счетом по физике ваниллы. AABB: редактор хитбоксов и их отрисовка - Протокол: Hitbox (имя, зона, min/max, поворот), пакеты Hitboxes, HitboxInstances и HitboxesReload. - Сервер: HitboxRegistry читает JSON-файл (world/hitboxes.json, можно указать --hitboxes), перечитывает его по запросу клиента и шлет наборы игрокам. Сломанный файл не стирает рабочие коробки, а объясняет причину. - Клиент: RsxHitboxDebug рисует коробки через RenderGlobal.drawBoundingBox с подписями DebugRenderer, красит зоны (голова красная, тело желтое, ноги синие). Команды: /rsx hitboxes on|off|reload|list|show NAME. - Новый модуль rsx-plugin-api с интерфейсом Hitboxes: плагин может зарегистрировать свои коробки. У сервера появился скрипт сборки. Тесты: протокол 34, сервер 80 (было 74). Форк собирается чисто, 3279 классов.
280 lines
10 KiB
Java
280 lines
10 KiB
Java
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<ModContainer> 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<String> 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);
|
|
}
|
|
}
|
|
} |