diff --git a/docs/apidoc/events.md b/docs/apidoc/events.md index f06d52a..189ab5e 100644 --- a/docs/apidoc/events.md +++ b/docs/apidoc/events.md @@ -49,6 +49,10 @@ Can only be used in the context of the dedicated server. More: [DedicatedServerD - `serverstart`: - Called when the dedicated server starts. - Event object is blank. +- `bootstrap`: + - Called when the dedicated server registers blocks, items, materials, enchantments, etc. + - This is when you should register cstom blocks and items. + - Event object is blank. - `serverstop`: - Called when the dedicated server stops. - Event object is blank. diff --git a/docs/apidoc/reflect.md b/docs/apidoc/reflect.md index 76a3318..0e0c31b 100644 --- a/docs/apidoc/reflect.md +++ b/docs/apidoc/reflect.md @@ -44,7 +44,9 @@ Each `ReflectClass` has the following properties: - List of all the static variable names for the class. - `staticVariables: Map` - key-value dictionary of all the static variables in a class. -- `superclass: String?` +- `superclass: Class?` + - The raw teavm class of the superclass. +- `superclassName: String?` - The class id of the class's superclass. Eg: `net.minecraft.client.entity.AbstractClientPlayer` - Will be `null` if `hasMeta` is equal to `false` diff --git a/examplemods/AsyncSink.js b/examplemods/AsyncSink.js index 100b792..b1455a1 100644 --- a/examplemods/AsyncSink.js +++ b/examplemods/AsyncSink.js @@ -40,6 +40,7 @@ ModAPI.meta.credits("By ZXMushroom63"); // @type Map AsyncSink.FS = new Map(); + AsyncSink.L10N = new Map(); AsyncSink.FSOverride = new Set(); AsyncSink.MIDDLEWARE = []; AsyncSink.setFile = function setFile(path, data) { @@ -149,7 +150,27 @@ ModAPI.meta.credits("By ZXMushroom63"); return originalFileExists.apply(this, args); }; + const L10NRead = ModAPI.util.getMethodFromPackage("net.minecraft.util.StatCollector", "translateToLocal"); + const originalL10NRead = ModAPI.hooks.methods[L10NRead]; + ModAPI.hooks.methods[L10NRead] = function (...args) { + var key = ModAPI.util.ustr(args[0]); + if (AsyncSink.L10N.has(key)) { + return ModAPI.util.str(AsyncSink.L10N.get(key)); + } + return originalL10NRead.apply(this, args); + }; + + const L10NCheck = ModAPI.util.getMethodFromPackage("net.minecraft.util.StatCollector", "canTranslate"); + const originalL10NCheck = ModAPI.hooks.methods[L10NRead]; + ModAPI.hooks.methods[L10NCheck] = function (...args) { + if (AsyncSink.L10N.has(ModAPI.util.ustr(args[0]))) { + return 1; + } + return originalL10NCheck.apply(this, args); + }; + globalThis.AsyncSink = AsyncSink; + ModAPI.events.newEvent("lib:asyncsink"); ModAPI.events.callEvent("lib:asyncsink", {}); console.log("[AsyncSink] Loaded!"); } @@ -225,6 +246,7 @@ ModAPI.meta.credits("By ZXMushroom63"); ModAPI.addEventListener("sendchatmessage", (e) => { if (e.message.toLowerCase().startsWith(".reload_tex")) { e.preventDefault = true; + ModAPI.mc.renderItem.itemModelMesher.simpleShapesCache.clear(); ModAPI.promisify(ModAPI.mc.refreshResources)(); } }); diff --git a/examplemods/block_of_steve.js b/examplemods/block_of_steve.js new file mode 100644 index 0000000..d2849af --- /dev/null +++ b/examplemods/block_of_steve.js @@ -0,0 +1,117 @@ +//nice little utility function to fix the block identity map +function fixupBlockIds() { + var blockRegistry = ModAPI.util.wrap(ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.blockRegistry).getCorrective(); + var BLOCK_STATE_IDS = ModAPI.util.wrap(ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.BLOCK_STATE_IDS).getCorrective(); + blockRegistry.registryObjects.hashTableKToV.forEach(entry => { + if (entry) { + var block = entry.value; + var validStates = block.getBlockState().getValidStates(); + var stateArray = validStates.array || [validStates.element]; + stateArray.forEach(iblockstate => { + var i = blockRegistry.getIDForObject(block.getRef()) << 4 | block.getMetaFromState(iblockstate.getRef()); + BLOCK_STATE_IDS.put(iblockstate.getRef(), i); + }); + } + }); +} +function makeSteveBlock() { + var blockClass = ModAPI.reflect.getClassById("net.minecraft.block.Block"); + var iproperty = ModAPI.reflect.getClassById("net.minecraft.block.properties.IProperty").class; + var makeBlockState = ModAPI.reflect.getClassById("net.minecraft.block.state.BlockState").constructors.find(x => x.length === 2); + var blockSuper = ModAPI.reflect.getSuper(blockClass, (x) => x.length === 2); + var nmb_BlockSteve = function nmb_BlockSteve() { + blockSuper(this, ModAPI.materials.rock.getRef()); + } + ModAPI.reflect.prototypeStack(blockClass, nmb_BlockSteve); + nmb_BlockSteve.prototype.$isOpaqueCube = function () { + return 1; + } + nmb_BlockSteve.prototype.$createBlockState = function () { + return makeBlockState(this, ModAPI.array.object(iproperty, 0)); + } + globalThis.nmb_BlockSteve = nmb_BlockSteve; +} +function registerSteveClientSide() { + var itemClass = ModAPI.reflect.getClassById("net.minecraft.item.Item"); + var blockClass = ModAPI.reflect.getClassById("net.minecraft.block.Block"); + var block_of_steve = (new nmb_BlockSteve()).$setHardness(-1.0).$setStepSound(blockClass.staticVariables.soundTypeGravel).$setUnlocalizedName( + ModAPI.util.str("steve") + ); + blockClass.staticMethods.registerBlock0.method( + 198, + ModAPI.util.str("steve"), + block_of_steve + ); + itemClass.staticMethods.registerItemBlock0.method(block_of_steve); + ModAPI.addEventListener("lib:asyncsink", async () => { + AsyncSink.L10N.set("tile.steve.name", "Block Of Steve"); + AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/models/block/steve.json", JSON.stringify( + { + "parent": "block/cube_all", + "textures": { + "all": "blocks/steve" + } + } + )); + AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/models/item/steve.json", JSON.stringify( + { + "parent": "block/steve", + "display": { + "thirdperson": { + "rotation": [10, -45, 170], + "translation": [0, 1.5, -2.75], + "scale": [0.375, 0.375, 0.375] + } + } + } + )); + AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/blockstates/steve.json", JSON.stringify( + { + "variants": { + "normal": [ + { "model": "steve" }, + ] + } + } + )); + AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/textures/blocks/steve.png", await (await fetch( + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAE0SURBVDhPpdO9S8NAHMbxy3sVfJmMg6h7FRXXkkUUX0addSjo4OAfIDqLIoiLi3+BRRx0EIQOnV0EcVAIWkR0KIFgrcEktX6vcXD0nuE+5Afhnhw5bWy4qylaidOfVQhT0zFKYozjBHVdzi3TwCZvteaS/0fLD8oGf5OzTeyxNUyE3Ln2HmGctpuxKuS3wd76CgPHsrEj142NeojCkHsFry+4c3aJ6g1OtlZp0Ok4DD4i+Y2GIZ+DMMAhtw+fHu8xi3IDM9t5YfMQF71dLHo+ZjsfXbh4WtnH0vYaqp/BcXGGM3D7BxiYTi+el8uYZWm2gM/VB/Tfaqje4GB5iga2Jv+sUuUa5/ITmOXq7gbnC+MY1r9QvcHG9AgN0lRex1u/ilr7ehqWvBNZvMlRbESfqNhAiG/Pb1bHXpMbFgAAAABJRU5ErkJggg==" + )).arrayBuffer()); + }); +} +function registerSteveServerSide() { + function fixupBlockIds() { + var blockRegistry = ModAPI.util.wrap(ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.blockRegistry).getCorrective(); + var BLOCK_STATE_IDS = ModAPI.util.wrap(ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.BLOCK_STATE_IDS).getCorrective(); + blockRegistry.registryObjects.hashTableKToV.forEach(entry => { + if (entry) { + var block = entry.value; + var validStates = block.getBlockState().getValidStates(); + var stateArray = validStates.array || [validStates.element]; + stateArray.forEach(iblockstate => { + var i = blockRegistry.getIDForObject(block.getRef()) << 4 | block.getMetaFromState(iblockstate.getRef()); + BLOCK_STATE_IDS.put(iblockstate.getRef(), i); + }); + } + }); + } + var blockClass = ModAPI.reflect.getClassById("net.minecraft.block.Block"); + var itemClass = ModAPI.reflect.getClassById("net.minecraft.item.Item"); + ModAPI.addEventListener("bootstrap", () => { + var block_of_steve = (new nmb_BlockSteve()).$setHardness(-1.0).$setStepSound(blockClass.staticVariables.soundTypeGravel).$setUnlocalizedName( + ModAPI.util.str("steve") + ); + blockClass.staticMethods.registerBlock0.method( + 198, + ModAPI.util.str("steve"), + block_of_steve + ); + itemClass.staticMethods.registerItemBlock0.method(block_of_steve); + fixupBlockIds(); + }); +} +ModAPI.dedicatedServer.appendCode(makeSteveBlock); +makeSteveBlock(); +registerSteveClientSide(); +fixupBlockIds(); +ModAPI.dedicatedServer.appendCode(registerSteveServerSide); \ No newline at end of file diff --git a/index.html b/index.html index 27d2158..4eb6424 100644 --- a/index.html +++ b/index.html @@ -167,6 +167,7 @@ ModAPI.hooks._rippedData ||= []; ModAPI.hooks._teavm ||= {}; ModAPI.hooks._rippedConstructors ||= {}; + ModAPI.hooks._rippedInternalConstructors ||= {}; ModAPI.hooks.methods ||= {}; ModAPI.hooks._rippedMethodTypeMap ||= {}; ModAPI.hooks._postInit ||= ()=>{}; diff --git a/injector.js b/injector.js index 88877ef..96bfd39 100644 --- a/injector.js +++ b/injector.js @@ -136,6 +136,20 @@ var main;(function(){` ); } ); + + const extractInternalConstructorRegex = + /^\s*function (\S*?)__init_\d*?\(\$this/gm; //same as extract constructor regex, but only allow $this as first argument + patchedFile = patchedFile.replaceAll( + extractInternalConstructorRegex, + (match) => { + var fullName = match.match(extractConstructorFullNameRegex); + fullName = fullName[0].replace("function ", ""); + return ( + `ModAPI.hooks._rippedInternalConstructors[\`${fullName}\`] = ${fullName}; +` + match + ); + } + ); if(globalThis.optimizePi){ patchedFile = patchedFile.replaceAll( diff --git a/patches.js b/patches.js index 06ee7c6..8087300 100644 --- a/patches.js +++ b/patches.js @@ -35,4 +35,4 @@ PatchesRegistry.addPatch(function (input) { if (!$this.$renderHand)` ); return output; -}) +}); \ No newline at end of file diff --git a/postinit.js b/postinit.js index 640e3be..47f7e72 100644 --- a/postinit.js +++ b/postinit.js @@ -149,7 +149,7 @@ globalThis.modapi_postinit = "(" + (() => { } if (xOut && typeof xOut === "object" && !Array.isArray(xOut)) { if (corrective) { - return new Proxy(outputValue.data, CorrectiveRecursive); + return new Proxy(xOut, CorrectiveRecursive); } return new Proxy(xOut, ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf); } @@ -209,6 +209,7 @@ globalThis.modapi_postinit = "(" + (() => { ModAPI.hooks.regenerateClassMap = function () { ModAPI.hooks._rippedConstructorKeys = Object.keys(ModAPI.hooks._rippedConstructors); + ModAPI.hooks._rippedInternalConstructorKeys = Object.keys(ModAPI.hooks._rippedInternalConstructors); ModAPI.hooks._rippedMethodKeys = Object.keys(ModAPI.hooks._rippedMethodTypeMap); var compiledNames = new Set(); @@ -255,6 +256,7 @@ globalThis.modapi_postinit = "(" + (() => { "id": classId, "binaryName": item?.$meta?.binaryName || null, "constructors": [], + "internalConstructors": [], "methods": {}, "staticMethods": {}, "staticVariables": {}, @@ -268,9 +270,11 @@ globalThis.modapi_postinit = "(" + (() => { } } if (typeof item?.$meta?.superclass === "function" && item?.$meta?.superclass?.$meta) { - ModAPI.hooks._classMap[compiledName].superclass = item.$meta.superclass.$meta.name; + ModAPI.hooks._classMap[compiledName].superclassName = item.$meta.superclass.$meta.name; + ModAPI.hooks._classMap[compiledName].superclass = item.$meta.superclass; } else { ModAPI.hooks._classMap[compiledName].superclass = null; + ModAPI.hooks._classMap[compiledName].superclassName = null; } ModAPI.hooks._classMap[compiledName].staticVariableNames = ModAPI.hooks._rippedStaticIndexer[compiledName]; ModAPI.hooks._classMap[compiledName].staticVariables = ModAPI.hooks._rippedStaticProperties[compiledName]; @@ -286,6 +290,13 @@ globalThis.modapi_postinit = "(" + (() => { } }); } + + ModAPI.hooks._rippedInternalConstructorKeys.forEach(initialiser => { // Find internal constructors/initialisers. Used for calling super() on custom classes. (They are the different implementations of a classes constructor, that don't automatically create an object. Thus, it is identical to calling super) + if (initialiser.startsWith(compiledName + "__init_") && !initialiser.includes("$lambda$")) { + ModAPI.hooks._classMap[compiledName].internalConstructors.push(ModAPI.hooks._rippedInternalConstructors[initialiser]); + } + }); + ModAPI.hooks._rippedMethodKeys.forEach((method) => { if (method.startsWith(compiledName + "_") && !method.includes("$lambda$")) { var targetMethodMap = ModAPI.hooks._classMap[compiledName].methods; @@ -319,6 +330,31 @@ globalThis.modapi_postinit = "(" + (() => { var key = classKeys.filter(k => { return ModAPI.hooks._classMap[k].name === className })[0]; return key ? ModAPI.hooks._classMap[key] : null; } + + //Magical function for making a subclass with a custom constructor that you can easily use super(...) on. + ModAPI.reflect.getSuper = function getSuper(reflectClass, filter) { + filter ||= ()=>true; + var initialiser = reflectClass.internalConstructors.find(filter); + return function superFunction(thisArg, ...extra_args) { + reflectClass.class.call(thisArg); + initialiser(thisArg, ...extra_args); + } + } + + //Iteratively load the superclasses' prototype methods. + ModAPI.reflect.prototypeStack = function prototypeStack(reflectClass, classFn) { + var stack = [reflectClass.class.prototype]; + var currentSuperclass = reflectClass.superclass; + while (currentSuperclass) { + stack.push(currentSuperclass.prototype); + currentSuperclass = currentSuperclass?.$meta?.superclass; + } + stack.reverse(); + stack.forEach(proto => { + Object.assign(classFn.prototype, proto); + }); + } + var reloadDeprecationWarnings = 0; const TeaVMArray_To_Recursive_BaseData_ProxyConf = { get(target, prop, receiver) { @@ -556,7 +592,7 @@ globalThis.modapi_postinit = "(" + (() => { //Function used for running @Async / @Async-dependent TeaVM methods. ModAPI.promisify = function promisify(fn) { - return function promisifiedJavaMethpd(...inArguments) { + return function promisifiedJavaMethod(...inArguments) { return new Promise((res, rej) => { Promise.resolve().then( //queue microtask () => { @@ -850,10 +886,12 @@ globalThis.modapi_postinit = "(" + (() => { ModAPI.enchantments = new Proxy(ModAPI.hooks._classMap[ModAPI.util.getCompiledName("net.minecraft.enchantment.Enchantment")].staticVariables, StaticProps_ProxyConf); } + ModAPI.events.newEvent("bootstrap", "server"); const originalBootstrap = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.init.Bootstrap", "register")]; ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.init.Bootstrap", "register")] = function (...args) { var x = originalBootstrap.apply(this, args); ModAPI.util.bootstrap(); + ModAPI.events.callEvent("bootstrap", {}); console.log("[ModAPI] Hooked into bootstrap. .blocks, .items, .materials and .enchantments are now accessible."); return x; }