diff --git a/README.md b/README.md index 49515c0..3e3a4e7 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Download this repository as a .zip, and extract it. Open index.html with your pr - Run `npx efi /help` for use instructions. #### How does it work? -This tool matches patterns in eaglercraft builds and adds patching code to let you modify how the code works at runtime. It then adds a [corelib](./postinit.js) that initialises the `ModAPI` object. +This tool matches patterns in eaglercraft builds and adds patching code to let you modify how the code works at runtime. It then adds a [corelib](./core/postinit.js) that initialises the `ModAPI` object. #### History EaglerForgeInjector is a replacement for the `ModAPI` in the [old eaglerforge](https://github.com/EaglerForge/EaglerForge-old), which was maintained by @radmanplays. The legacy eaglerforge was a port of [Leah Anderson's EaglerReborn (dmca'd)](https://github.com/EaglerReborn/reborn)'s `PluginAPI` (created by me, @ZXMushroom63) to run on newer versions of Eaglercraft, with a few improvements and new features. Unlike EaglerForgeInjector, both legacy eaglerforge and eaglerreborn manually exposed properties and methods one by one. diff --git a/core/core.js b/core/core.js index 5136956..da4a1e4 100644 --- a/core/core.js +++ b/core/core.js @@ -37,7 +37,7 @@ var modapi_preinit = `globalThis.ModAPI ||= {}; `; var freezeCallstack = `if(ModAPI.hooks.freezeCallstack){return false};`; const EFIConfig = { - ModAPIVersion: "v2.7.92", //also change in package.json + ModAPIVersion: "v2.7.93", //also change in package.json doEaglerforge: true, verbose: false, doServerExtras: false, diff --git a/core/postinit.js b/core/postinit.js index 38e1fea..e6ae7e6 100644 --- a/core/postinit.js +++ b/core/postinit.js @@ -27,6 +27,7 @@ const modapi_postinit = "(" + (() => { ModAPI.meta._developerMap = {}; ModAPI.meta._iconMap = {}; ModAPI.meta._versionMap = {}; + ModAPI.isServer = false; const credits = {}; ModAPI.addCredit = function (category, name, contents) { if (!credits[category]) { @@ -36,7 +37,7 @@ const modapi_postinit = "(" + (() => { } function getCreditsString() { return Object.entries(credits).map((entry) => { - return " "+entry[0] + LF + " " + (new Array(entry[0].length)).fill("~").join("") + entry[1].join("") + LF + LF + LF; + return " " + entry[0] + LF + " " + (new Array(entry[0].length)).fill("~").join("") + entry[1].join("") + LF + LF + LF; }).join(""); } ModAPI.array = {}; @@ -140,6 +141,9 @@ const modapi_postinit = "(" + (() => { } }); } + function easyStaticPropAlias(clsId, real, alias) { + easyAlias(ModAPI.reflect.getClassById(clsId).staticVariables, real, alias); + } ModAPI.meta.title = function (title) { if (!document.currentScript || document.currentScript.getAttribute("data-isMod") !== "true") { return console.log("[ModAPIMeta] Cannot set meta for non-mod script."); @@ -216,6 +220,7 @@ const modapi_postinit = "(" + (() => { ModAPI.util.getMethodFromPackage = function (classId, methodName) { if (ModAPI.is_1_12) { classId = classId.replace(".eaglercraft.v1_8", ".eaglercraft"); //why peyton why must you do this. you couldve changed it to v1_12 too, that would've worked + classId = classId.replace(".entity.RenderItem", ".RenderItem"); } var name = ""; var classStuff = classId.split("."); @@ -571,6 +576,24 @@ const modapi_postinit = "(" + (() => { return target; } } + if (prop === "getCorrective") { + return function () { + return new Proxy(target, patchProxyConfToCorrective(TeaVMArray_To_Recursive_BaseData_ProxyConf)); + } + } + if (prop === "isCorrective") { + return function () { + return corrective; + } + } + if (prop === "reload") { + return function () { + if (reloadDeprecationWarnings < 10) { + console.warn("ModAPI/PluginAPI reload() is obsolete, please stop using it in code.") + reloadDeprecationWarnings++; + } + } + } var outputValue = Reflect.get(target, prop, receiver); var wrapped = ModAPI.util.wrap(outputValue, target, corrective, true); if (wrapped) { @@ -839,7 +862,11 @@ const modapi_postinit = "(" + (() => { var v = typeof param === "object" ? param.msg : (param + ""); v ||= ""; var jclString = ModAPI.util.string(v); - ModAPI.hooks.methods["nmcg_GuiNewChat_printChatMessage"](ModAPI.javaClient.$ingameGUI.$persistantChatGUI, ModAPI.hooks._classMap[ModAPI.util.getCompiledName("net.minecraft.util.ChatComponentText")].constructors[0](jclString)); + if (ModAPI.is_1_12) { + ModAPI.hooks.methods["nmcg_GuiNewChat_printChatMessage"](ModAPI.javaClient.$ingameGUI.$persistantChatGUI, ModAPI.hooks._classMap[ModAPI.util.getCompiledName("net.minecraft.util.text.TextComponentString")].constructors[0](jclString)); + } else { + ModAPI.hooks.methods["nmcg_GuiNewChat_printChatMessage"](ModAPI.javaClient.$ingameGUI.$persistantChatGUI, ModAPI.hooks._classMap[ModAPI.util.getCompiledName("net.minecraft.util.ChatComponentText")].constructors[0](jclString)); + } } ModAPI.util.makeArray = function makeArray(arrayClass, arrayContents = []) { @@ -959,7 +986,7 @@ const modapi_postinit = "(" + (() => { inlineIntegratedServerStartup = ModAPI.hooks._rippedMethodKeys.filter(key => { return key.startsWith(inlineIntegratedServerStartup); })[0]; const inlineIntegratedServerStartupMethod = ModAPI.hooks.methods[inlineIntegratedServerStartup]; ModAPI.hooks.methods[inlineIntegratedServerStartup] = function (worker, bootstrap) { - var x = inlineIntegratedServerStartupMethod.apply(this, [worker, bootstrap + ";" + globalThis.modapi_postinit + ";" + ModAPI.dedicatedServer._data.join(";")]); + var x = inlineIntegratedServerStartupMethod.apply(this, [worker, bootstrap + ";" + globalThis.modapi_postinit + ";ModAPI.isServer=true;" + ModAPI.dedicatedServer._data.join(";")]); ModAPI.dedicatedServer._data = []; ModAPI.dedicatedServer._wasUsed = true; console.log("[ModAPI] Hooked into inline integrated server."); @@ -1112,6 +1139,11 @@ const modapi_postinit = "(" + (() => { } else { ModAPI.enchantments = new Proxy(ModAPI.reflect.getClassById("net.minecraft.enchantment.Enchantment").staticVariables, StaticProps_ProxyConf); } + + if (ModAPI.is_1_12) { + //1.12 specific globals + ModAPI.blockSounds = new Proxy(ModAPI.reflect.getClassById("net.minecraft.block.SoundType").staticVariables, StaticProps_ProxyConf); + } } ModAPI.events.newEvent("bootstrap", "server"); @@ -1193,11 +1225,16 @@ const modapi_postinit = "(" + (() => { // 1.12 utility junk if (ModAPI.is_1_12) { + var Block = ModAPI.reflect.getClassById("net.minecraft.block.Block").class; var NonNullList = ModAPI.reflect.getClassById("net.minecraft.util.NonNullList").class; var ArrayAsList = ModAPI.reflect.getClassById("java.util.Arrays$ArrayAsList").class; + easyStaticPropAlias("net.minecraft.block.Block", "REGISTRY", "blockRegistry"); + easyStaticPropAlias("net.minecraft.item.Item", "REGISTRY", "itemRegistry"); } - + function qhash(txt, arr, interval) { + arr ||= []; + interval ||= 32767; // var interval = 4095; //used to be 4095 - arr.length, but that increases incompatibility based on load order and other circumstances if (arr.length >= interval) { console.error("[ModAPI.keygen] Ran out of IDs while generating for " + txt); @@ -1216,6 +1253,7 @@ const modapi_postinit = "(" + (() => { } return hash; } + ModAPI.util.qhash = qhash; ModAPI.keygen = {}; @@ -1226,11 +1264,19 @@ const modapi_postinit = "(" + (() => { return registryNamespaceMethod.apply(this, args); } ModAPI.keygen.item = function (item) { - var values = [...ModAPI.reflect.getClassById("net.minecraft.item.Item").staticVariables.itemRegistry.$modapi_specmap.values()]; + if (ModAPI.is_1_12) { + var values = ModAPI.util.wrap(ModAPI.reflect.getClassById("net.minecraft.item.Item").staticVariables.REGISTRY).getCorrective().underlyingIntegerMap.identityMap.elementData.filter(x => !!x).map(y => y.value.value); + } else { + var values = [...ModAPI.reflect.getClassById("net.minecraft.item.Item").staticVariables.itemRegistry.$modapi_specmap.values()]; + } return qhash(item, values, 4095); } ModAPI.keygen.block = function (block) { - var values = [...ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.blockRegistry.$modapi_specmap.values()]; + if (ModAPI.is_1_12) { + var values = ModAPI.util.wrap(ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.REGISTRY).getCorrective().underlyingIntegerMap.identityMap.elementData.filter(x => !!x).map(y => y.value.value); + } else { + var values = [...ModAPI.reflect.getClassById("net.minecraft.block.Block").staticVariables.blockRegistry.$modapi_specmap.values()]; + } return qhash(block, values, 4095); } ModAPI.keygen.entity = function (entity) { @@ -1248,10 +1294,27 @@ const modapi_postinit = "(" + (() => { } return qhash(entity, values, 127); } + ModAPI.keygen.sound = function (soundId) { + if (!ModAPI.is_1_12) { + return -1; + } + const SoundEvent = ModAPI.reflect.getClassByName("SoundEvent") + const values = ModAPI.util.wrap(SoundEvent.staticVariables.REGISTRY).getCorrective().underlyingIntegerMap.identityMap.elementData.filter(x => !!x).map(y => y.value.value); + return qhash(soundId, values, 4095); + } + ModAPI.keygen.enchantment = function (enchantment) { + const Enchantment = ModAPI.reflect.getClassByName("Enchantment"); + if (ModAPI.is_1_12) { + const values = ModAPI.util.wrap(Enchantment.staticVariables.REGISTRY).getCorrective().underlyingIntegerMap.identityMap.elementData.filter(x => !!x).map(y => y.value.value); + return qhash(enchantment, values, 4095); + } + const values = ModAPI.util.wrap(Enchantment.staticVariables.enchantmentsList).getCorrective().filter(x => !!x).map(y => y.effectId); + return qhash(enchantment, values, 4095); + } }).toString() + ")();"; if (globalThis.process) { module.exports = { modapi_postinit: modapi_postinit } - } \ No newline at end of file +} \ No newline at end of file diff --git a/docs/apidoc/index.md b/docs/apidoc/index.md index 5e4fe28..32fbdb2 100644 --- a/docs/apidoc/index.md +++ b/docs/apidoc/index.md @@ -34,6 +34,8 @@ The global object has the following properties: - This is a key-value dictionary of all of the enchantments in the game. It is generated upon init from the static variables of the `Enchantment` class. - For example, to access the enchantment class for `knockback`, you can use `ModAPI.enchantments["knockback"]` - As the enchantment class has other static variables, `Object.keys` will also return non-enchantment keys such as `enchantmentsBookList`. + - `ModAPI.blockSounds: Map` (1.12 only) + - This is a key-value dictionary of `SoundType`s. Used in `.setSoundType(soundType)` when registering custom blocks in 1.12. - `ModAPI.minecraft: Minecraft` - This is the minecraft instance for the client, generated upon init. - It can also be accessed using `ModAPI.mc` @@ -81,6 +83,8 @@ The global object has the following properties: - The version of ModAPI. - `ModAPI.is_1_12: Boolean` - Property defining wether or not ModAPI thinks the current version is 1.12-based. + - `ModAPI.isServer: Boolean` + - Property defining wether or not ModAPI thinks it is running on the dedicated server. - `ModAPI.flavour: String` - The flavour of ModAPI. Hardcoded to be `"injector"`. diff --git a/docs/apidoc/keygen.md b/docs/apidoc/keygen.md index 1e3a401..7a2656a 100644 --- a/docs/apidoc/keygen.md +++ b/docs/apidoc/keygen.md @@ -7,4 +7,8 @@ Methods: - `ModAPI.keygen.block(blockId: String) : number` - Example usage is: `var id = ModAPI.keygen.block("my_example_block");` - `ModAPI.keygen.entity(entityId: String) : number` - - Example usage is: `var id = ModAPI.keygen.entity("my_example_entity");` \ No newline at end of file + - Example usage is: `var id = ModAPI.keygen.entity("my_example_entity");` +- `ModAPI.keygen.sound(soundId: String) : number` + - Example usage is: `var id = ModAPI.keygen.sound("my.example.sound");` +- `ModAPI.keygen.enchantment(enchantmentId: String) : number` + - Example usage is: `var id = ModAPI.keygen.enchantment("sharpness_v2");` \ No newline at end of file diff --git a/examplemods/AsyncSink.js b/examplemods/AsyncSink.js index f5c1b08..bb17309 100644 --- a/examplemods/AsyncSink.js +++ b/examplemods/AsyncSink.js @@ -11,7 +11,7 @@ ModAPI.meta.credits("By ZXMushroom63"); if (ModAPI.is_1_12) { const _boolRes = ModAPI.reflect.getClassById("net.lax1dude.eaglercraft.internal.teavm.BooleanResult").constructors[0]; - booleanResult = (b) => _boolRes(b*1); + booleanResult = (b) => _boolRes(b * 1); } else { booleanResult = (b) => ModAPI.hooks.methods.nlevit_BooleanResult__new(b * 1); } @@ -196,6 +196,26 @@ ModAPI.meta.credits("By ZXMushroom63"); return originalL18NFormat.apply(this, args); }; + const LanguageMapTranslate = ModAPI.util.getMethodFromPackage("net.minecraft.util.text.translation.LanguageMap", "tryTranslateKey"); + const originalLanguageMapTranslate = ModAPI.hooks.methods[LanguageMapTranslate]; + ModAPI.hooks.methods[LanguageMapTranslate] = function (...args) { + var key = ModAPI.util.ustr(args[1]); + if (AsyncSink.L10N.has(key)) { + args[1] = ModAPI.util.str(AsyncSink.L10N.get(key)); + } + return originalLanguageMapTranslate.apply(this, args); + }; + + const LanguageMapCheckTranslate = ModAPI.util.getMethodFromPackage("net.minecraft.util.text.translation.LanguageMap", "isKeyTranslated"); + const originalLanguageMapCheckTranslate = ModAPI.hooks.methods[LanguageMapCheckTranslate]; + ModAPI.hooks.methods[LanguageMapCheckTranslate] = function (...args) { + var key = ModAPI.util.ustr(args[1]); + if (AsyncSink.L10N.has(key)) { + return 1; + } + return originalLanguageMapTranslate.apply(this, args); + }; + const L10NCheck = ModAPI.util.getMethodFromPackage("net.minecraft.util.StatCollector", "canTranslate"); const originalL10NCheck = ModAPI.hooks.methods[L10NCheck]; ModAPI.hooks.methods[L10NCheck] = function (...args) { @@ -217,7 +237,7 @@ ModAPI.meta.credits("By ZXMushroom63"); async function assureAsyncSinkResources() { const dec = new TextDecoder("utf-8"); const enc = new TextEncoder("utf-8"); - var resourcePackKey = ModAPI.is_1_12 ? "_net_lax1dude_eaglercraft_v1_8_internal_PlatformFilesystem_1_12_2_" :(await indexedDB.databases()).find(x => x?.name?.endsWith("_resourcePacks")).name; + var resourcePackKey = ModAPI.is_1_12 ? "_net_lax1dude_eaglercraft_v1_8_internal_PlatformFilesystem_1_12_2_" : (await indexedDB.databases()).find(x => x?.name?.endsWith("_resourcePacks")).name; const dbRequest = indexedDB.open(resourcePackKey); const db = await promisifyIDBRequest(dbRequest); const transaction = db.transaction(["filesystem"], "readonly"); @@ -326,8 +346,8 @@ ModAPI.meta.credits("By ZXMushroom63"); // values = SoundEntry[] // category: AsyncSink.Audio.Category.* // SoundEntry = {path: String, pitch: 1, volume: 1, streaming: false} - const EaglercraftRandom = ModAPI.reflect.getClassByName("EaglercraftRandom").constructors.find(x=>x.length===0); - + const EaglercraftRandom = ModAPI.reflect.getClassByName("EaglercraftRandom").constructors.find(x => x.length === 0); + function makeSoundEventAccessor(soundpoolentry, weight) { const SoundEventAccessorClass = ModAPI.reflect.getClassByName("SoundEventAccessor").class; var object = new SoundEventAccessorClass; @@ -336,7 +356,7 @@ ModAPI.meta.credits("By ZXMushroom63"); wrapped.weight = weight; return object; } - + function makeSoundEventAccessorComposite(rKey, pitch, volume, category) { const SoundEventAccessorCompositeClass = ModAPI.reflect.getClassByName("SoundEventAccessorComposite").class; var object = new SoundEventAccessorCompositeClass; @@ -349,12 +369,12 @@ ModAPI.meta.credits("By ZXMushroom63"); wrapped.rnd = EaglercraftRandom(); return object; } - function makeSoundEventAccessor112(rKey) { + function makeSoundEventAccessor112(rKey, subttl) { const SoundEventAccessorClass = ModAPI.reflect.getClassByName("SoundEventAccessor").class; var object = new SoundEventAccessorClass; var wrapped = ModAPI.util.wrap(object).getCorrective(); wrapped.location = rKey; - wrapped.subtitle = null; + wrapped.subtitle = ModAPI.util.str(subttl); wrapped.accessorList = ModAPI.hooks.methods.cgcc_Lists_newArrayList0(); wrapped.rnd = EaglercraftRandom(); return object; @@ -362,32 +382,44 @@ ModAPI.meta.credits("By ZXMushroom63"); if (ModAPI.is_1_12) { const soundType = ModAPI.reflect.getClassById("net.minecraft.client.audio.Sound$Type").staticVariables.FILE; const mkSound = ModAPI.reflect.getClassById("net.minecraft.client.audio.Sound").constructors[0]; - - AsyncSink.Audio.register = function addSfx(key, category, values) { - if (!category) { - throw new Error("[AsyncSink] Invalid audio category provided: "+category); + const SoundEvent = ModAPI.reflect.getClassById("net.minecraft.util.SoundEvent"); + const SoundEvents = ModAPI.reflect.getClassById("net.minecraft.init.SoundEvents"); + + AsyncSink.Audio.register = function addSfx(key, unused, values, subtitle) { + subtitle ||= "(AsyncSink Sound)"; + if (unused) { + console.log("Category is not a property of the sound in 1.12, category for " + key + " will be unused."); } + + const rKey = ResourceLocation(ModAPI.util.str(key)); + + const ev = new SoundEvent.class; + ev.$soundName = rKey; + ModAPI.util.wrap(SoundEvent.staticVariables.REGISTRY).register(ModAPI.keygen.sound(key), rKey, ev); + const outObj = SoundEvents.staticMethods.getRegisteredSoundEvent.method(ModAPI.util.str(key)); + var snd = ModAPI.mc.mcSoundHandler.getCorrective(); var registry = snd.soundRegistry.soundRegistry; - var rKey = ResourceLocation(ModAPI.util.str(key)); + + var soundPool = values.map(se => { - return mkSound(ModAPI.util.str(se.path), se.volume, se.pitch, 1, soundType, 1 * se.streaming); + return mkSound(ModAPI.util.str(se.path.replace("sounds/", "").replace(".ogg", "")), se.volume, se.pitch, 1, soundType, 1 * se.streaming); }); - var eventAccessor = makeSoundEventAccessor112(rKey); + var eventAccessor = makeSoundEventAccessor112(rKey, subtitle); var eventAccessorWrapped = ModAPI.util.wrap(eventAccessor); soundPool.forEach(sound => { eventAccessorWrapped.accessorList.add(sound); }); AsyncSink.Audio.Objects.push([rKey, eventAccessor]); registry.put(rKey, eventAccessor); - values.map(x=>"resourcepacks/AsyncSinkLib/assets/minecraft/" + x.path + ".mcmeta").forEach(x=>AsyncSink.setFile(x, new ArrayBuffer(0))); - return soundPool; + values.map(x => "resourcepacks/AsyncSinkLib/assets/minecraft/" + x.path + ".mcmeta").forEach(x => AsyncSink.setFile(x, new ArrayBuffer(0))); + return outObj; } } else { const SoundPoolEntry = ModAPI.reflect.getClassByName("SoundPoolEntry").constructors.find(x => x.length === 4); AsyncSink.Audio.register = function addSfx(key, category, values) { if (!category) { - throw new Error("[AsyncSink] Invalid audio category provided: "+category); + throw new Error("[AsyncSink] Invalid audio category provided: " + category); } var snd = ModAPI.mc.mcSoundHandler; var registry = snd.sndRegistry.soundRegistry; @@ -405,7 +437,7 @@ ModAPI.meta.credits("By ZXMushroom63"); }); AsyncSink.Audio.Objects.push([rKey, compositeSound]); registry.put(rKey, compositeSound); - values.map(x=>"resourcepacks/AsyncSinkLib/assets/minecraft/" + x.path + ".mcmeta").forEach(x=>AsyncSink.setFile(x, new ArrayBuffer(0))); + values.map(x => "resourcepacks/AsyncSinkLib/assets/minecraft/" + x.path + ".mcmeta").forEach(x => AsyncSink.setFile(x, new ArrayBuffer(0))); return soundPool; } } diff --git a/examplemods/custom_block_112.js b/examplemods/custom_block_112.js new file mode 100644 index 0000000..ac8833c --- /dev/null +++ b/examplemods/custom_block_112.js @@ -0,0 +1,122 @@ +//THIS IS A DEMO MOD + + +//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 blockSuper = ModAPI.reflect.getSuper(blockClass, (x) => x.length === 2); + var creativeBlockTab = ModAPI.reflect.getClassById("net.minecraft.creativetab.CreativeTabs").staticVariables.tabBlock; + function nmb_BlockSteve() { + blockSuper(this, ModAPI.materials.rock.getRef()); + //this.$defaultBlockState = this.$blockState.$getBaseState(); built-in in 1.12 + this.$setCreativeTab(creativeBlockTab); + } + ModAPI.reflect.prototypeStack(blockClass, nmb_BlockSteve); + 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).$setSoundType(ModAPI.blockSounds.PLANT.getRef()).$setUnlocalizedName( + ModAPI.util.str("steve") + ); + blockClass.staticMethods.registerBlock0.method( + ModAPI.keygen.block("steve"), + ModAPI.util.str("steve"), + block_of_steve + ); + itemClass.staticMethods.registerItemBlock0.method(block_of_steve); + ModAPI.addEventListener("lib:asyncsink", async () => { + ModAPI.addEventListener("lib:asyncsink:registeritems", (renderItem)=>{ + console.log("registered!!"); + renderItem.registerBlock(block_of_steve, ModAPI.util.str("steve")); + }); + 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()); + }); + ModAPI.blocks["steve"] = block_of_steve; +} +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).$setSoundType(ModAPI.blockSounds.PLANT.getRef()).$setUnlocalizedName( + ModAPI.util.str("steve") + ); + blockClass.staticMethods.registerBlock0.method( + ModAPI.keygen.block("steve"), //MAXIMUM BLOCK ID IS 4095. keygen method accounts for this + ModAPI.util.str("steve"), + block_of_steve + ); + itemClass.staticMethods.registerItemBlock0.method(block_of_steve); + fixupBlockIds(); + ModAPI.blocks["steve"] = block_of_steve; + }); +} +ModAPI.dedicatedServer.appendCode(makeSteveBlock); +makeSteveBlock(); +registerSteveClientSide(); +fixupBlockIds(); + +ModAPI.dedicatedServer.appendCode(registerSteveServerSide); \ No newline at end of file diff --git a/examplemods/timescale_command.js b/examplemods/timescale_command.js index 4a66a16..e52facd 100644 --- a/examplemods/timescale_command.js +++ b/examplemods/timescale_command.js @@ -2,20 +2,28 @@ ModAPI.meta.title("Timescale Command"); ModAPI.meta.description("/timescale 0.5 to halve the speed of time"); ModAPI.meta.credits("By ZXMushroom63"); + globalThis.timeScale = 1n; + globalThis.timeScaleDividing = false; PluginAPI.addEventListener("sendchatmessage", (event) => { if (event.message.toLowerCase().startsWith("/timescale")) { var speed = parseFloat(event.message.split(" ")[1]); if (!speed) { - speed = 1; + globalThis.timeScale = 1n; + globalThis.timeScaleDividing = false; PluginAPI.mc.timer.timerSpeed = 1; } else { if (speed < 1) { - speed = 1 / Math.round(1 / speed); + globalThis.timeScaleDividing = true; + globalThis.timeScale = BigInt(Math.round(1 / speed)); } else { - speed = Math.round(speed); + globalThis.timeScaleDividing = false; + globalThis.timeScale = BigInt(Math.round(speed)); } PluginAPI.mc.timer.timerSpeed = speed; } + PluginAPI.mc.systemTime = PluginAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.Minecraft", "getSystemTime")](); + PluginAPI.mc.debugUpdateTime = PluginAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.Minecraft", "getSystemTime")]() - 1000n; + PluginAPI.mc.timer.lastSyncSysClock = PluginAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.Minecraft", "getSystemTime")](); PluginAPI.displayToChat("[Timescale] Set world timescale to " + speed.toFixed(2) + "."); } }); @@ -52,4 +60,12 @@ } }; }); + const original_getSystemTime = ModAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.Minecraft", "getSystemTime")]; + PluginAPI.hooks.methods[ModAPI.util.getMethodFromPackage("net.minecraft.client.Minecraft", "getSystemTime")] = function () { + if (globalThis.timeScaleDividing) { + return original_getSystemTime() / globalThis.timeScale; + } else { + return original_getSystemTime() * globalThis.timeScale; + } + }; })(); \ No newline at end of file diff --git a/examplemods/useless_item_example_mod_112.js b/examplemods/useless_item_example_mod_112.js new file mode 100644 index 0000000..5868327 --- /dev/null +++ b/examplemods/useless_item_example_mod_112.js @@ -0,0 +1,69 @@ +// This is an example mod on how to register an item. +(()=>{ + const itemTexture = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAKZJREFUOE9j/P//PxMDBIBoEP6HREOl4PLIciA2AyPIgMcM//7KgvWSDJjBBpx9/+YvJzc3Sbq12DhB6sEGsJ19/+YnmQawYhigzc7FcPXnN4KugbqAHWQAy9n3b34T4wJkw6EGYLqAoNVQBWS5ANlwZBfAvUCs/0EGkW0AzBKqGoCSDgh5A80F2KMRpAgfAKUT6kcjsfEPUycmKMQgy8AETkgUZWcAS3CPIf4oSPsAAAAASUVORK5CYII="; + //this texture is REALLY bad, so the item appears 2d in game. (it uses partially transparent pixels around the edges in some spots ;-;) + ModAPI.meta.title("Adding items demo."); + ModAPI.meta.version("v1.0"); + ModAPI.meta.icon(itemTexture); + ModAPI.meta.description("Requires AsyncSink."); + + function ExampleItem() { + var creativeMiscTab = ModAPI.reflect.getClassById("net.minecraft.creativetab.CreativeTabs").staticVariables.MISC; + var itemClass = ModAPI.reflect.getClassById("net.minecraft.item.Item"); + var itemSuper = ModAPI.reflect.getSuper(itemClass, (x) => x.length === 1); + function nmi_ItemExample() { + itemSuper(this); //Use super function to get block properties on this class. + this.$setCreativeTab(creativeMiscTab); + } + ModAPI.reflect.prototypeStack(itemClass, nmi_ItemExample); + + function internal_reg() { + var example_item = (new nmi_ItemExample()).$setUnlocalizedName( + ModAPI.util.str("exampleitem") + ); + itemClass.staticMethods.registerItem.method(ModAPI.keygen.item("exampleitem"), ModAPI.util.str("exampleitem"), example_item); + ModAPI.items["exampleitem"] = example_item; + + return example_item; + } + + if (ModAPI.items) { + return internal_reg(); + } else { + ModAPI.addEventListener("bootstrap", internal_reg); + } + } + + ModAPI.dedicatedServer.appendCode(ExampleItem); + var example_item = ExampleItem(); + + ModAPI.addEventListener("lib:asyncsink", async () => { + ModAPI.addEventListener("lib:asyncsink:registeritems", (renderItem)=>{ + renderItem.registerItem(example_item, ModAPI.util.str("exampleitem")); + }); + AsyncSink.L10N.set("item.exampleitem.name", "Example Item"); + AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/models/item/exampleitem.json", JSON.stringify( + { + "parent": "builtin/generated", + "textures": { + "layer0": "items/exampleitem" + }, + "display": { + "thirdperson": { + "rotation": [ -90, 0, 0 ], + "translation": [ 0, 1, -3 ], + "scale": [ 0.55, 0.55, 0.55 ] + }, + "firstperson": { + "rotation": [ 0, -135, 25 ], + "translation": [ 0, 4, 2 ], + "scale": [ 1.7, 1.7, 1.7 ] + } + } + } + )); + AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/textures/items/exampleitem.png", await (await fetch( + itemTexture + )).arrayBuffer()); + }); +})(); \ No newline at end of file diff --git a/package.json b/package.json index cfafd3a..8b8fb69 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eaglerforgeinjector", - "version": "2.7.92", + "version": "2.7.93", "description": "Advanced modding API injector for unminified, unobfuscated, unsigned eaglercraft builds.", "main": "node.js", "directories": {