From b3752f20af344031321fc3e10183c54aea9885a6 Mon Sep 17 00:00:00 2001 From: ZXMushroom63 Date: Sun, 8 Sep 2024 11:53:05 +0800 Subject: [PATCH] lib events, custom events, custom items demp code --- docs/apidoc/events.md | 15 +++++- examplemods/CustomItemsDemo.js | 28 ++++++++++++ examplemods/lib.customitems.js | 83 +++++++++++++++++++++++++++++++++- postinit.injector.js | 48 +++++++++++++++----- postinit.js | 40 ++++++++++++---- 5 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 examplemods/CustomItemsDemo.js diff --git a/docs/apidoc/events.md b/docs/apidoc/events.md index 75094e9..ee6df14 100644 --- a/docs/apidoc/events.md +++ b/docs/apidoc/events.md @@ -72,7 +72,7 @@ Can only be used in the context of the dedicated server. More: [DedicatedServerD The events global, `ModAPI.events`, allows you to register new event types and call them. #### ModAPI.events.newEvent(eventName: String) -You can register new events using ModAPI, as long as the event name starts with `custom:`. For example, if I want to add a new event that can be used by other mods, I can use `ModAPI.events.newEvent("custom:myevent")`. +You can register new events using ModAPI, as long as the event name starts with `custom:` (`lib:` is only useful for library loading). For example, if I want to add a new event that can be used by other mods, I can use `ModAPI.events.newEvent("custom:myevent")`. #### ModAPI.events.callEvent(eventName: String, data: Object) @@ -91,4 +91,17 @@ ModAPI.events.newEvent("custom:myevent"); ModAPI.events.callEvent("custom:myevent", { secretCode: "1234" }); +``` + +#### Using library load events +```javascript +// Mod #2, registers and calls lib event +ModAPI.events.newEvent("lib:mylibrary:loaded"); +ModAPI.events.callEvent("lib:mylibrary:loaded", {}); +``` +```javascript +// Mod #1, registers event handler for lib event +ModAPI.addEventListener("lib:mylibrary:loaded", (e)=>{ + //Lib events function differently to normal events, as when they are called once, any new event listener with automatically fire upon being registered. +}); ``` \ No newline at end of file diff --git a/examplemods/CustomItemsDemo.js b/examplemods/CustomItemsDemo.js new file mode 100644 index 0000000..cc624dc --- /dev/null +++ b/examplemods/CustomItemsDemo.js @@ -0,0 +1,28 @@ +//This mod also requires lib.customitems.js +ModAPI.addEventListener("lib:libcustomitems:loaded", () => { + console.log("Registered my cool custom item."); + LibCustomItems.registerItem({ + tag: "mymod:test_item_1", + base: "magma_cream", + name: "Custom Item", + qty: 32, + recipe: [ + "###", + "# #", + "###" + ], + recipeLegend: { + "#": { + "type": "block", + "id": "dirt" + } + }, + onRightClickGround: `/*/user, world, itemstack, blockpos/*/ + itemstack.stackSize -= 1; + if (itemstack.stackSize < 1) { + user.inventory.mainInventory[user.inventory.currentItem] = null; + } + user.setHealth(2); + ` + }); +}); \ No newline at end of file diff --git a/examplemods/lib.customitems.js b/examplemods/lib.customitems.js index 0c02d49..0dac7aa 100644 --- a/examplemods/lib.customitems.js +++ b/examplemods/lib.customitems.js @@ -1,4 +1,85 @@ // Library to make adding custom items with EaglerForgeInjector much easier. (function LibItems() { - ModAPI.events.newEvent() + ModAPI.events.newEvent("lib:libcustomitems:loaded"); + function libServerside() { + globalThis.LCI_REGISTRY ||= []; + globalThis.LCI_ACTIONREGISTRY ||= {}; + var useName = ModAPI.util.getMethodFromPackage("net.minecraft.network.NetHandlerPlayServer", "processPlayerBlockPlacement"); + var oldUse = ModAPI.hooks.methods[useName]; + ModAPI.hooks.methods[useName] = function ($this, packet) { + if ($this?.$playerEntity?.$inventory && $this.$playerEntity.$inventory.$getCurrentItem()) { + var item = $this.$playerEntity.$inventory.$getCurrentItem(); + if (item.$stackTagCompound && item.$stackTagCompound.$hasKey(ModAPI.util.str("display"), 10)) { + var displayTag = item.$stackTagCompound.$getCompoundTag(ModAPI.util.str("display")); + if (displayTag.$hasKey(ModAPI.util.str("Lore"), 9)) { + var loreTag = displayTag.$getTag(ModAPI.util.str("Lore")); + if (loreTag.$tagCount() > 0 && globalThis.LCI_REGISTRY.includes(ModAPI.util.ustr(loreTag.$getStringTagAt(0)))) { + var cid = loreTag.$getStringTagAt(0); + var positionTag = Object.keys(packet).filter(x => { return x.startsWith("$position") })[0]; + if (packet[positionTag].$x === -1 && packet[positionTag].$y === -1 && packet[positionTag].$z === -1) { + return 0; + } + globalThis.LCI_ACTIONREGISTRY[cid].call(globalThis, + new Proxy($this.$playerEntity, ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf), + new Proxy($this.$serverController.$worldServerForDimension($this.$playerEntity.$dimension), ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf), + new Proxy(item, ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf), + new Proxy(packet[positionTag], ModAPI.util.TeaVM_to_Recursive_BaseData_ProxyConf)); + return 0; + } + } + } + } + return oldUse.apply(this, [$this, packet]); + } + } + function LCI_registerItem(data) { + globalThis.LCI_REGISTRY ||= []; + globalThis.LCI_ACTIONREGISTRY ||= {}; + globalThis.LCI_REGISTRY.push(data.tag); + globalThis.LCI_ACTIONREGISTRY[data.tag] = new Function("user", "world", "itemstack", "blockpos", data.onRightClickGround); + var ObjectClass = ModAPI.reflect.getClassById("java.lang.Object").class; + function ToChar(char) { + return ModAPI.reflect.getClassById("java.lang.Character").staticMethods.valueOf.method(char[0].charCodeAt(0)); + } + ModAPI.dedicatedServer.appendCode(`(function () { + function handler() { + LCI_registerItem(${JSON.stringify(data)}); + ModAPI.removeEventListener("tick", handler); + } + ModAPI.addEventListener("tick", handler); + })()`); + var recipeInternal = []; + Object.keys(data.recipeLegend).forEach((key) => { + recipeInternal.push(ToChar(key)); + var ingredient = null; + var schema = data.recipeLegend[key]; + if (schema.type === "block") { + ingredient = ModAPI.blocks[schema.id].getRef(); + } else { + ingredient = ModAPI.items[schema.id].getRef(); + } + recipeInternal.push(ingredient); + }); + var recipeContents = data.recipe.flatMap(x => { return ModAPI.util.str(x) }); + var recipe = ModAPI.util.makeArray(ObjectClass, recipeContents.concat(recipeInternal)); + + var testItem = ModAPI.reflect.getClassById("net.minecraft.item.ItemStack").constructors[4](ModAPI.items[data.base].getRef(), data.qty); + testItem.$stackTagCompound = ModAPI.reflect.getClassById("net.minecraft.nbt.NBTTagCompound").constructors[0](); + testItem.$stackTagCompound.$setTag(ModAPI.util.str("display"), ModAPI.reflect.getClassById("net.minecraft.nbt.NBTTagCompound").constructors[0]()); + var displayTag = testItem.$stackTagCompound.$getCompoundTag(ModAPI.util.str("display")); + displayTag.$setString(ModAPI.util.str("Name"), ModAPI.util.str(data.name)); + var lore = ModAPI.reflect.getClassById("net.minecraft.nbt.NBTTagList").constructors[0](); + lore.$appendTag(ModAPI.reflect.getClassById("net.minecraft.nbt.NBTTagString").constructors.filter(x => { return x.length === 1 })[0](ModAPI.util.str(data.tag))); + displayTag.$setTag(ModAPI.util.str("Lore"), lore); + + var craftingManager = ModAPI.reflect.getClassById("net.minecraft.item.crafting.CraftingManager").staticMethods.getInstance.method(); + ModAPI.hooks.methods.nmic_CraftingManager_addRecipe(craftingManager, testItem, recipe); + } + ModAPI.dedicatedServer.appendCode(libServerside); + ModAPI.dedicatedServer.appendCode("globalThis.LCI_registerItem = " + LCI_registerItem.toString()); + window.LibCustomItems = {}; + LibCustomItems.registerItem = function register(data) { + LCI_registerItem(data); + } + ModAPI.events.callEvent("lib:libcustomitems:loaded", {}); })(); \ No newline at end of file diff --git a/postinit.injector.js b/postinit.injector.js index 87db7b2..b0d58f0 100644 --- a/postinit.injector.js +++ b/postinit.injector.js @@ -288,12 +288,25 @@ globalThis.modapi_postinit = `(() => { ModAPI.required = new Set(); ModAPI.events = {}; ModAPI.events.types = ["event"]; + ModAPI.events.lib_map = {}; ModAPI.events.listeners = { "event": [] }; ModAPI.addEventListener = function addEventListener(name, callback) { + if (name.startsWith("lib:")) { + if (ModAPI.events.lib_map[name]) { + callback({}); + } else { + if (!Array.isArray(ModAPI.events.listeners[name])) { + ModAPI.events.listeners[name] = []; + } + ModAPI.events.listeners[name].push(callback); + } + console.log("[ModAPI] Added new library listener."); + return; + } if (!callback || typeof callback !== "function") { throw new Error("[ModAPI] Invalid callback!"); } - if (ModAPI.events.types.includes(name) || name.startsWith("custom:")) { + if (ModAPI.events.types.includes(name) || name.startsWith("custom:") || name.startsWith("lib:")) { if (!Array.isArray(ModAPI.events.listeners[name])) { ModAPI.events.listeners[name] = []; } @@ -327,9 +340,11 @@ globalThis.modapi_postinit = `(() => { }); } }; - ModAPI.events.newEvent = function newEvent(name, side) { - console.log("[ModAPI] Registered " + side + " event: " + name); - ModAPI.events.types.push(name); + ModAPI.events.newEvent = function newEvent(name, side = "unknown") { + if (!ModAPI.events.types.includes(name)) { + console.log("[ModAPI] Registered " + side + " event: " + name); + ModAPI.events.types.push(name); + } }; ModAPI.events.callEvent = function callEvent(name, data) { @@ -352,12 +367,19 @@ globalThis.modapi_postinit = `(() => { console.error("Please report this bug to the repo."); return; } - ModAPI.events.listeners[name].forEach((func) => { - func(data); - }); - ModAPI.events.listeners.event.forEach((func) => { - func({ event: name, data: data }); - }); + if (name.startsWith("lib:")) { + ModAPI.events.listeners[name].forEach((func) => { + func({}); + }); + ModAPI.events.lib_map[name] = true; + } else { + ModAPI.events.listeners[name].forEach((func) => { + func(data); + }); + ModAPI.events.listeners.event.forEach((func) => { + func({ event: name, data: data }); + }); + } }; ModAPI.events.newEvent("update", "client"); @@ -413,6 +435,10 @@ globalThis.modapi_postinit = `(() => { 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 = []) { + return ModAPI.hooks._teavm.$rt_createArrayFromData(arrayClass, arrayContents); + } + ModAPI.clickMouse = function () { ModAPI.hooks.methods["nmc_Minecraft_clickMouse"](ModAPI.javaClient); } @@ -442,7 +468,7 @@ globalThis.modapi_postinit = `(() => { return x; }; - var integratedServerStartup = ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.sp.internal.ClientPlatformSingleplayer", "createBlobObj"); + var integratedServerStartup = ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.sp.internal.ClientPlatformSingleplayer", "loadIntegratedServerSourceInline"); //Integrated server setup has a randomised suffix on the end integratedServerStartup = ModAPI.hooks._rippedMethodKeys.filter(key => { return key.startsWith(integratedServerStartup); })[0]; const integratedServerStartupMethod = ModAPI.hooks.methods[integratedServerStartup]; diff --git a/postinit.js b/postinit.js index 13fdd76..c865c00 100644 --- a/postinit.js +++ b/postinit.js @@ -288,12 +288,25 @@ ModAPI.required = new Set(); ModAPI.events = {}; ModAPI.events.types = ["event"]; + ModAPI.events.lib_map = {}; ModAPI.events.listeners = { "event": [] }; ModAPI.addEventListener = function addEventListener(name, callback) { + if (name.startsWith("lib:")) { + if (ModAPI.events.lib_map[name]) { + callback({}); + } else { + if (!Array.isArray(ModAPI.events.listeners[name])) { + ModAPI.events.listeners[name] = []; + } + ModAPI.events.listeners[name].push(callback); + } + console.log("[ModAPI] Added new library listener."); + return; + } if (!callback || typeof callback !== "function") { throw new Error("[ModAPI] Invalid callback!"); } - if (ModAPI.events.types.includes(name) || name.startsWith("custom:")) { + if (ModAPI.events.types.includes(name) || name.startsWith("custom:") || name.startsWith("lib:")) { if (!Array.isArray(ModAPI.events.listeners[name])) { ModAPI.events.listeners[name] = []; } @@ -354,12 +367,19 @@ console.error("Please report this bug to the repo."); return; } - ModAPI.events.listeners[name].forEach((func) => { - func(data); - }); - ModAPI.events.listeners.event.forEach((func) => { - func({ event: name, data: data }); - }); + if (name.startsWith("lib:")) { + ModAPI.events.listeners[name].forEach((func) => { + func({}); + }); + ModAPI.events.lib_map[name] = true; + } else { + ModAPI.events.listeners[name].forEach((func) => { + func(data); + }); + ModAPI.events.listeners.event.forEach((func) => { + func({ event: name, data: data }); + }); + } }; ModAPI.events.newEvent("update", "client"); @@ -415,6 +435,10 @@ 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 = []) { + return ModAPI.hooks._teavm.$rt_createArrayFromData(arrayClass, arrayContents); + } + ModAPI.clickMouse = function () { ModAPI.hooks.methods["nmc_Minecraft_clickMouse"](ModAPI.javaClient); } @@ -444,7 +468,7 @@ return x; }; - var integratedServerStartup = ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.sp.internal.ClientPlatformSingleplayer", "createBlobObj"); + var integratedServerStartup = ModAPI.util.getMethodFromPackage("net.lax1dude.eaglercraft.v1_8.sp.internal.ClientPlatformSingleplayer", "loadIntegratedServerSourceInline"); //Integrated server setup has a randomised suffix on the end integratedServerStartup = ModAPI.hooks._rippedMethodKeys.filter(key => { return key.startsWith(integratedServerStartup); })[0]; const integratedServerStartupMethod = ModAPI.hooks.methods[integratedServerStartup];