diff --git a/docs/apidoc/utils.md b/docs/apidoc/utils.md index 8446a71..fa80f21 100644 --- a/docs/apidoc/utils.md +++ b/docs/apidoc/utils.md @@ -43,4 +43,16 @@ Methods: - `ModAPI.util.wrap(obj: Object) : object` - Returns a wrapper around native java objects, removing prefixes and fixing method outputs. - `ModAPI.util.getNearestProperty(object: Object, property: string) : string` - - Finds the nearest property name to the one you specify (suffix based). This is used to mitigate teavm adding random suffixes to properties. \ No newline at end of file + - Finds the nearest property name to the one you specify (suffix based). This is used to mitigate teavm adding random suffixes to properties. +- `ModAPI.util.modifyFunction(fn: Function, patcherFunction: Function) : string` + - Returns a modifies version of a function, where the patcher function can be used to modify the contents of a function. Example: + ```javascript + function add(a, b) { + return a + b; + } + var multiply = ModAPI.util.modifyFunction(add, (code)=>{ + return code.replaceAll("a + b", "a * b"); + }); + console.log(multiply(2, 3)); + //Logs 6 + ``` \ No newline at end of file diff --git a/examplemods/astar.js b/examplemods/astar.js new file mode 100644 index 0000000..93ef7cf --- /dev/null +++ b/examplemods/astar.js @@ -0,0 +1,117 @@ +(function AStarPathfinding() { + ModAPI.meta.title("A* Pathfinding Bot"); + ModAPI.meta.description("Use #move to instruct the bot to move somewhere. Use #stop to terminate the job."); + ModAPI.meta.credits("By ZXMushroom63"); + + ModAPI.require("player"); + ModAPI.require("world"); + + var blockBreakCostMultiplier = 2; + const costMap = Object.fromEntries(Object.keys(ModAPI.blocks).flatMap(x => { + ModAPI.blocks[x].readableId = x; + return [x, (Math.floor(ModAPI.blocks[x].blockHardness * 10 * blockBreakCostMultiplier) + 1) || 99999]; //Block hardness is in decimals, unbreakable blocks are negative one, and base movement cost is 1. -1 + 1 = 0, and 0 || 99999 is 99999 + })); + + var makeBlockPos = ModAPI.reflect.getClassById("net.minecraft.util.BlockPos").constructors.find(x=>x.length === 3); + + function shouldPause(x, y, z) { + return !ModAPI.world.isAreaLoaded0(makeBlockPos(x, y, z), 2); + } + + class APNode { + constructor(x, y, z, g, h, parent = null) { + this.x = x; + this.y = y; + this.z = z; + this.g = g; + this.h = h; + this.f = g + h; + this.parent = parent; + } + } + + function heuristic(a, b) { + return Math.abs(a.x - b.x) + Math.abs(a.y - b.y) + Math.abs(a.z - b.z); + } + + function getNeighbors(node) { + const neighbors = []; + const directions = [ + [1, 0, 0], [-1, 0, 0], + [0, 1, 0], [0, -1, 0], + [0, 0, 1], [0, 0, -1] + ]; + + for (const [dx, dy, dz] of directions) { + const x = node.x + dx; + const y = node.y + dy; + const z = node.z + dz; + + if (ModAPI.world.isBlockLoaded(makeBlockPos(Math.round(x), Math.round(y), Math.round(z)))) { + neighbors.push(new APNode(x, y, z, 0, 0)); + } + } + + return neighbors; + } + + function lookupCost(x, y, z) { + var block = ModAPI.world.getBlockState(makeBlockPos(Math.round(x), Math.round(y), Math.round(z))).block; + return costMap[block.readableId]; + } + + function* aStar(start, goal) { + const openSet = []; + const closedSet = new Set(); + openSet.push(start); + + while (openSet.length > 0) { + let current = openSet.reduce((prev, curr) => (prev.f < curr.f ? prev : curr)); + + if (current.x === goal.x && current.y === goal.y && current.z === goal.z) { + const path = []; + while (current) { + path.push([current.x, current.y, current.z]); + current = current.parent; + } + yield* path.reverse(); + return; + } + + openSet.splice(openSet.indexOf(current), 1); + closedSet.add(`${current.x},${current.y},${current.z}`); + + for (const neighbor of getNeighbors(current)) { + if (closedSet.has(`${neighbor.x},${neighbor.y},${neighbor.z}`)) { + continue; + } + + const tentativeG = current.g + lookupCost(neighbor.x, neighbor.y, neighbor.z); + + if (!openSet.some(node => node.x === neighbor.x && node.y === neighbor.y && node.z === neighbor.z)) { + neighbor.g = tentativeG; + neighbor.h = heuristic(neighbor, goal); + neighbor.f = neighbor.g + neighbor.h; + neighbor.parent = current; + openSet.push(neighbor); + } else { + const existingNode = openSet.find(node => node.x === neighbor.x && node.y === neighbor.y && node.z === neighbor.z); + if (tentativeG < existingNode.g) { + existingNode.g = tentativeG; + existingNode.f = existingNode.g + existingNode.h; + existingNode.parent = current; + } + } + } + + yield [current.x, current.y, current.z]; + } + + return []; + } + + const start = new APNode(0, 0, 0, 0, 0); + const goal = new APNode(2, 2, 2, 0, 0); + + const pathGenerator = aStar(start, goal); +})(); \ No newline at end of file diff --git a/examplemods/threadtesting.js b/examplemods/threadtesting.js index 1b0a633..35006aa 100644 --- a/examplemods/threadtesting.js +++ b/examplemods/threadtesting.js @@ -1,4 +1,4 @@ -//SUCCESS - While there is no TeaVM thread actively running, I am able to run an asyncronous function, and get a result. +//SUCCESS - While there is no TeaVM thread actively running, I am able to run an asynchronous function, and get a result. ModAPI.hooks._teavm.$rt_startThread(() => { return ModAPI.hooks.methods.nlevi_PlatformRuntime_downloadRemoteURI(ModAPI.util.str("data:text/plain,hi")) }, function (...args) { console.log(this, args) }) diff --git a/postinit.js b/postinit.js index e5f7e0d..5801eb0 100644 --- a/postinit.js +++ b/postinit.js @@ -584,6 +584,25 @@ globalThis.modapi_postinit = "(" + (() => { })[0] || null; } + ModAPI.util.modifyFunction = function (fn, patcherFn) { + // Convert the original function to a string + let functionString = fn.toString(); + + // Extract the function body + let bodyStart = functionString.indexOf('{') + 1; + let bodyEnd = functionString.lastIndexOf('}'); + let functionBody = functionString.substring(bodyStart, bodyEnd); + + // Replace the function body with new instructions + let modifiedFunctionBody = patcherFn(functionBody) || 'return;'; + + // Create a new function with the same arguments and the modified body + let args = functionString.substring(functionString.indexOf('(') + 1, functionString.indexOf(')')); + let modifiedFunction = new Function(args, modifiedFunctionBody); + + return modifiedFunction; + } + ModAPI.clickMouse = function () { ModAPI.hooks.methods["nmc_Minecraft_clickMouse"](ModAPI.javaClient); } @@ -661,6 +680,26 @@ globalThis.modapi_postinit = "(" + (() => { return sendChatMessage.apply(this, [$this, $message]); } + ModAPI.events.newEvent("render", "client"); + const renderMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.client.renderer.EntityRenderer", "renderWorldPass"); + const renderMethod = ModAPI.hooks.methods[renderMethodName]; + ModAPI.hooks.methods[renderMethodName] = function ($this, $int_pass, $float_partialTicks, $long_finishTimeNano) { + var shouldRenderHand = $this.$renderHand; + $this.$renderHand = 0; //Rendering the hand clears the depth bit, which we don't want to do. + var out = renderMethod.apply(this, [$this, $int_pass, $float_partialTicks, $long_finishTimeNano]); + var data = { + partialTicks: $float_partialTicks + } + ModAPI.events.callEvent("render", data); + if (shouldRenderHand) { + ModAPI.hooks.methods.nlevo_GlStateManager_clear(256); //GL_DEPTH_BUFFER_BIT, found in class RealOpenGLEnums + ModAPI.hooks.methods.nmcr_EntityRenderer_renderHand($this, $float_partialTicks, $int_pass); + ModAPI.hooks.methods.nmcr_EntityRenderer_renderWorldDirections($this, $float_partialTicks); + } + $this.$renderHand = shouldRenderHand; + return out; + } + const ScaledResolutionConstructor = ModAPI.reflect.getClassByName("ScaledResolution").constructors[0]; ModAPI.events.newEvent("frame", "client"); const frameMethodName = ModAPI.util.getMethodFromPackage("net.minecraft.client.Minecraft", "runTick");