Typescript definition for minecraft and LiquidBounce
-
Update: Experimental Support For ScriptAPI
minecraft-LBNG-types
This repo contains
- instruction and scripts for creating
- typescript definitions for Minecraft (with mods*)
- typescript definitions for LiquidBounce NextGen
- typescript definitions for embedded context of LiquidBounce NextGen scriptAPI
- a set of manually maintained patches to make the script api work properly
- a set of examples LiquidBounce NextGen script using typescript
- a compiler script that will compile all the .ts files into .js files that could run in graaljs (LiquidBounce NextGen runtime)
Note: the mods are only limited to those presented at the development environment of LiquidBounce NextGen.
When writing your script in typescript, expect inconsistencies with script API in js, but please report them to this repo if you can. But the following example already works(tested)
See also: This amazing plugin that colors each (member)variable to different colors similar to KDevelop
Original:
Typescript definition for minecraft and LiquidBounce (and virtually every mod installed plus java/kotlin infrustructure). The definitions will not be uploaded for potential legal risks (Although I don't really think that would be a violation or smth, I am not lawyer tho, just trying to be safe from distributing the game). See the demo video.
But
- it must be launched from the development environment to generate the definition(for now, will change if I decide to use the remapper or someone else whoever want to make it work can try.)
- there are ofc many nuances not dealt with (typescript -> graaljs).
- there is not yet a fully working project that can convert this to a script that LB compatible js (convert the import for certain prefixes to Java.type()"
- source mapping for the previous process
- it cannot properly deal with graaljs integration (implicit conversions for sometimes),
Any | null | any
and something like that. - Other problems with kotlin reflection vs java reflection but idk about those undiscovered traps.
This is a demo just for showing what is possible, certainly not as good as you think it is
Resources:
Download the ts-generator(FOSS, GPLv3 and Apache 2.0 dual licensed) and extract the zip, or ofc build the jar yourself. create a types-gen folder at the same path, modify the path in the script. Or read the script and support yourself, whatever.
The script for generating the definition:
// Import required Java classes const System = Java.type('java.lang.System'); const URLClassLoader = Java.type('java.net.URLClassLoader'); const File = Java.type('java.io.File'); const URL = Java.type('java.net.URL'); const Thread = Java.type('java.lang.Thread'); const Paths = Java.type('java.nio.file.Paths'); // Function to create a URLClassLoader from a JAR path function createClassLoaderFromJar(jarPath) { try { // Create File object for the JAR const jarFile = new File(jarPath); // Convert File to URL const jarUrl = jarFile.toURI().toURL(); // Create URLClassLoader with the system class loader as parent return new URLClassLoader([jarUrl], Thread.currentThread().getContextClassLoader()); } catch (e) { console.error('Error creating ClassLoader:', e); throw e; } } // Function to load a class from a given ClassLoader function loadClassFromJar(classLoader, className) { try { return classLoader.loadClass(className); } catch (e) { console.error(`Error loading class ${className}:`, e); throw e; } } const script = registerScript({ name: "for-repl", version: "1.0.0", authors: ["commandblock2"] }); script.registerModule({ name: "for-repl", category: "Client", description: "Sausage" }, (mod) => { mod.on("enable", (event) => { if (!mc.player || !mc.world) return; // This is working but lags a lot // The following will be fixed in this pr loader = createClassLoaderFromJar('/path/tots-generator-1.0.0.jar') NPMGen = loadClassFromJar(loader, "me.commandblock2.tsGenerator.NPMPackageGenerator") NPMGenKt = loadClassFromJar(loader, "me.commandblock2.tsGenerator.NPMPackageGeneratorKt") TsGen = loadClassFromJar(loader, "me.ntrrgc.tsGenerator.TypeScriptGenerator") VoidType = loadClassFromJar(loader, "me.ntrrgc.tsGenerator.VoidType") NULL = VoidType.getEnumConstants()[0] Map = Java.type("java.util.HashMap") ArrayList = Java.type("java.util.ArrayList") JvmClassMappingKt = Java.type('kotlin.jvm.JvmClassMappingKt'); kClass = JvmClassMappingKt.getKotlinClass(Client.class); classes = new ArrayList([kClass]); try { generated = new TsGen(classes, new Map(), new ArrayList(), new ArrayList(), "number", NULL); npmPack = new NPMGen(generated, "minecraft-yarn-definitions"); npmPack.writePackageTo(Paths.get("/path/totypes-gen")); console.log(generated); } catch (e) { e.printStackTrace() console.error(e); throw e; } // ReflectionUtil.invokeMethod(mc.player, "getName") // event.context.drawGuiTexture(RenderLayer.getGuiTextured, CLOSE_TEXTURE, 0, 0, 16, 16, -1); }) });
- instruction and scripts for creating
-
Update: now the script will also print the embedded context made available by the script api
// Import required Java classes // type: array /** @type any[]*/ var globalEntries = Object.entries(this); const System = Java.type("java.lang.System"); const URLClassLoader = Java.type("java.net.URLClassLoader"); const File = Java.type("java.io.File"); const URL = Java.type("java.net.URL"); const Thread = Java.type("java.lang.Thread"); const Paths = Java.type("java.nio.file.Paths"); const Map = Java.type("java.util.HashMap"); const ArrayList = Java.type("java.util.ArrayList"); const JvmClassMappingKt = Java.type("kotlin.jvm.JvmClassMappingKt"); const Class = Java.type("java.lang.Class"); // Function to create a URLClassLoader from a JAR path function createClassLoaderFromJar(jarPath) { try { // Create File object for the JAR const jarFile = new File(jarPath); // Convert File to URL const jarUrl = jarFile.toURI().toURL(); // Create URLClassLoader with the system class loader as parent return new URLClassLoader( [jarUrl], Thread.currentThread().getContextClassLoader() ); } catch (e) { console.error("Error creating ClassLoader:", e); throw e; } } // Function to load a class from a given ClassLoader function loadClassFromJar(classLoader, className) { try { return classLoader.loadClass(className); } catch (e) { console.error(`Error loading class ${className}:`, e); throw e; } } const script = registerScript({ name: "for-repl", version: "1.0.0", authors: ["commandblock2"], }); script.registerModule( { name: "for-repl", category: "Client", description: "Sausage", settings: { path: Setting.text({ name: "Path", default: "/mnt/old-linux/home/commandblock2/git_repo/MultiMC5/build/instances/LBNG-Production/minecraft/LiquidBounce/scripts", }), packageName: Setting.text({ name: "NPMPackageName", default: "minecraft-yarn-definitions", }), }, }, (mod) => { mod.on("enable", (event) => { const loader = createClassLoaderFromJar( mod.settings.path.value + "/ts-generator-1.0.0.jar" ); const NPMGen = loadClassFromJar( loader, "me.commandblock2.tsGenerator.NPMPackageGenerator" ); const TsGen = loadClassFromJar( loader, "me.ntrrgc.tsGenerator.TypeScriptGenerator" ); const VoidType = loadClassFromJar( loader, "me.ntrrgc.tsGenerator.VoidType" ); const NULL = VoidType.getEnumConstants()[0]; const javaClasses = globalEntries .filter((entry) => entry[1] != undefined) .map((entry) => (entry[1] instanceof Class ? entry[1] : entry[1].class)) .filter((entry) => entry != undefined); const kotlinClasses = javaClasses.map((entry) => JvmClassMappingKt.getKotlinClass(entry) ); const classes = new ArrayList(kotlinClasses); try { const generated = new TsGen( classes, new Map(), new ArrayList(), new ArrayList(), "number", NULL ); const npmPack = new NPMGen(generated, mod.settings.packageName.value); npmPack.writePackageTo( Paths.get(mod.settings.path.value + "/types-gen") ); const embeddedDefinition = ` // imports ${javaClasses .map((clazz) => { return `import { ${clazz.simpleName} } from "@${mod.settings.packageName.value}/types/${clazz.name.replaceAll(".", "/")}";`; }) .join("\n")} // definitions for objects ${globalEntries .filter((entry) => entry[1] != undefined) .filter((entry) => !(entry[1] instanceof Class)) .filter((entry) => entry[1].class != undefined) .map((entry) => `export var ${entry[0]}: ${entry[1].class.simpleName};`) .join("\n\n")} `; console.log(embeddedDefinition) } catch (e) { e.printStackTrace(); console.error(e); throw e; } // ReflectionUtil.invokeMethod(mc.player, "getName") // event.context.drawGuiTexture(RenderLayer.getGuiTextured, CLOSE_TEXTURE, 0, 0, 16, 16, -1); }); } );
-
Update: subsequent updates will be available on https://github.com/commandblock2/minecraft-LBNG-types
and the generated script was reverted to a state where it was not as readable as the following example (for correctness) but still largely remained the same.
Now it can kinda generate a working script, but the ts file has a few squiggles.
template.ts
// imports import { Setting, Vec3i, Vec3d, MathHelper, BlockPos, Hand, RotationAxis, mc, Client, RotationUtil, ItemUtil, NetworkUtil, InteractionUtil, BlockUtil, MovementUtil, ReflectionUtil, ParameterValidator, UnsafeThread, registerScript } from "@embedded"; import { ScriptModule } from "@minecraft-yarn-definitions/types/net/ccbluex/liquidbounce/script/bindings/features/ScriptModule"; import { Matrix2d } from "@minecraft-yarn-definitions/types/org/joml/Matrix2d"; const script = registerScript.apply({ name: "template", version: "1.0.0", authors: ["commandblock2"] }); script.registerModule({ // @ts-ignore name: "example-from-template", // @ts-ignore description: "Ths is an example module generated in ts", // @ts-ignore category: "Client" }, (mod: ScriptModule) => { mod.on("enable", () => { Client.displayChatMessage(`${mc.player}`) Client.displayChatMessage(`${new Vec3i(1, 2, 3)}`) Client.displayChatMessage(`${new Matrix2d(1.2, 1.3, 1.4, 15)}`) Client.displayChatMessage("enabled") }) mod.on("disable", () => Client.displayChatMessage("disabled")) })
compiled template.js
function __require(path) { if (path.startsWith("@embedded")) { return { _embedded: globalThis } } if (path.startsWith("@minecraft-yarn-definitions/types/")) { return { [path.substring(path.lastIndexOf("/") + 1)]: Java.type(path .replaceAll("@minecraft-yarn-definitions/types/", "") .replaceAll("/", ".") ) } } return require(path); } var exports = {} "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const { _embedded } = __require("@embedded"); const { Matrix2d } = __require("@minecraft-yarn-definitions/types/org/joml/Matrix2d"); const script = _embedded.registerScript.apply({ name: "template", version: "1.0.0", authors: ["commandblock2"] }); script.registerModule({ // @ts-ignore name: "example-from-template", // @ts-ignore description: "Ths is an example module generated in ts", // @ts-ignore category: "Client" }, (mod) => { mod.on("enable", () => { _embedded.Client.displayChatMessage(`${_embedded.mc.player}`); _embedded.Client.displayChatMessage(`${new Vec3i(1, 2, 3)}`); _embedded.Client.displayChatMessage(`${new Matrix2d(1.2, 1.3, 1.4, 15)}`); _embedded.Client.displayChatMessage("enabled"); }); mod.on("disable", () => _embedded.Client.displayChatMessage("disabled")); });