Skip to content

Commit

Permalink
Merge pull request #32 from eaglerforge/main
Browse files Browse the repository at this point in the history
ModAPI v2.2
  • Loading branch information
ZXMushroom63 authored Nov 5, 2024
2 parents 7093527 + 85ac018 commit a07c320
Show file tree
Hide file tree
Showing 14 changed files with 587 additions and 33 deletions.
4 changes: 4 additions & 0 deletions docs/apidoc/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 9 additions & 2 deletions docs/apidoc/reflect.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ Methods:
- This method is used to find a class by its id.
- For example, to get the `Minecraft` class, you can use `ModAPI.reflect.getClassById("Minecraft")`
- This runs slower than `getClassById` because it has to filter through all classes. Make sure to cache the result rather than calling it over and over again.

- `ModAPI.reflect.getSuper(rClass: ReflectClass, filter: Function) : Function`
- Gets a super function from a reflect class. This is used to extend built in classes, like `Block`.
- For an example, see lines [29](https://github.com/eaglerforge/EaglerForgeInjector/blob/6e8598c180f96a65c0c101be72e6d0fa53195404/examplemods/unlucky_blocks.js#L29) and [33](https://github.com/eaglerforge/EaglerForgeInjector/blob/6e8598c180f96a65c0c101be72e6d0fa53195404/examplemods/unlucky_blocks.js#L33) in unlucky_blocks.js
- `ModAPI.reflect.prototypeStack(rClass: ReflectClass, target: Class/ConstructorFunction) : void`
- Copies methods from a reflect class and it's parents onto a target native JavaScript class. This allows TeaVM to use these objects normally, without you having to manually reimplement every method. In other words, this is the equivalent of extending a class.
- [Example usage](https://github.com/eaglerforge/EaglerForgeInjector/blob/6e8598c180f96a65c0c101be72e6d0fa53195404/examplemods/unlucky_blocks.js#L37)
### ReflectClass Definition

Each `ReflectClass` has the following properties:
Expand Down Expand Up @@ -44,7 +49,9 @@ Each `ReflectClass` has the following properties:
- List of all the static variable names for the class.
- `staticVariables: Map<String, *>`
- 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`

Expand Down
2 changes: 2 additions & 0 deletions docs/apidoc/utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Methods:
- `ModAPI.util.isCritical() : boolean`
- Checks wether the thread is in a critical state.
- When patching methods, it is good practice to allow the method to resume as usual if this is `true`, to avoid stack implosions. (yes, those are real)
- `ModAPI.util.bootstrap() : void`
- Regenerate proxies for ModAPI.items, .blocks, .materials, .enchantments
- `ModAPI.util.createArray(class, jsArray) : Object[]`
- Makes a java array from a class and a javascript array.
- The class parameter can be retrieved via reflect: `ModAPI.reflect.getClassById("net.minecraft.util.BlockPos").class`
Expand Down
1 change: 1 addition & 0 deletions docs/tutorials/comingsoon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Coming Soon
119 changes: 109 additions & 10 deletions examplemods/AsyncSink.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
ModAPI.meta.title("AsyncSink");
ModAPI.meta.description("Library for patching and hooking into asynchronous filesystem requests for EaglercraftX.");
ModAPI.meta.icon("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAL9JREFUOE9jZGBg+M9ABcAIMsgtPo3hzZ2zYONEVIxJZu9aOIsBbJCRtTHcEJAgLgBSh82ic0fPIgyCKQAJXrx4EcUsfX19sBiIRrYU5gu4Qchew2cQyHSQYehBgdNruFwEcybMZci+gIcRIa+hhxu6LzBiDZvX0A1BDyuivYbLIJK8pqevjze5GlsbMxAdayCT/PQwDRS2gaQror2m36KH4SqjZybwxEl0gsQWRkM01ogpVQh6jaJihBgXEFIDAAIQ9AFDJlrxAAAAAElFTkSuQmCC");
const asyncSinkIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAL9JREFUOE9jZGBg+M9ABcAIMsgtPo3hzZ2zYONEVIxJZu9aOIsBbJCRtTHcEJAgLgBSh82ic0fPIgyCKQAJXrx4EcUsfX19sBiIRrYU5gu4Qchew2cQyHSQYehBgdNruFwEcybMZci+gIcRIa+hhxu6LzBiDZvX0A1BDyuivYbLIJK8pqevjze5GlsbMxAdayCT/PQwDRS2gaQror2m36KH4SqjZybwxEl0gsQWRkM01ogpVQh6jaJihBgXEFIDAAIQ9AFDJlrxAAAAAElFTkSuQmCC";
ModAPI.meta.icon(asyncSinkIcon);
ModAPI.meta.credits("By ZXMushroom63");
(function AsyncSinkFn() {
//AsyncSink is a plugin to debug and override asynchronous methods in EaglercraftX
function runtimeComponent() {
async function runtimeComponent() {
const booleanResult = (b) => ModAPI.hooks.methods.nlevit_BooleanResult__new(b * 1);
const wrap = ModAPI.hooks.methods.otji_JSWrapper_wrap;
const unwrap = ModAPI.hooks.methods.otji_JSWrapper_unwrap;
Expand Down Expand Up @@ -39,6 +40,7 @@ ModAPI.meta.credits("By ZXMushroom63");

// @type Map<string, ArrayBuffer>
AsyncSink.FS = new Map();
AsyncSink.L10N = new Map();
AsyncSink.FSOverride = new Set();
AsyncSink.MIDDLEWARE = [];
AsyncSink.setFile = function setFile(path, data) {
Expand Down Expand Up @@ -80,8 +82,8 @@ ModAPI.meta.credits("By ZXMushroom63");
}
return wrap(AsyncSink.getFile(ModAPI.util.ustr(args[1])));
}
var ev = {method: "read", file: ModAPI.util.ustr(args[1]), shim: false, shimOutput: new ArrayBuffer()};
AsyncSink.MIDDLEWARE.forEach((fn)=>{fn(ev)});
var ev = { method: "read", file: ModAPI.util.ustr(args[1]), shim: false, shimOutput: new ArrayBuffer() };
AsyncSink.MIDDLEWARE.forEach((fn) => { fn(ev) });
if (ev.shim) {
return wrap(ev.shimOutput);
}
Expand All @@ -100,8 +102,8 @@ ModAPI.meta.credits("By ZXMushroom63");
AsyncSink.setFile(ModAPI.util.ustr(args[1]), args[2]);
return booleanResult(true);
}
var ev = {method: "write", file: ModAPI.util.ustr(args[1]), data: args[2], shim: false, shimOutput: true};
AsyncSink.MIDDLEWARE.forEach((fn)=>{fn(ev)});
var ev = { method: "write", file: ModAPI.util.ustr(args[1]), data: args[2], shim: false, shimOutput: true };
AsyncSink.MIDDLEWARE.forEach((fn) => { fn(ev) });
if (ev.shim) {
return booleanResult(ev.shimOutput);
}
Expand All @@ -120,8 +122,8 @@ ModAPI.meta.credits("By ZXMushroom63");
AsyncSink.deleteFile(ModAPI.util.ustr(args[1]));
return booleanResult(true);
}
var ev = {method: "delete", file: ModAPI.util.ustr(args[1]), shim: false, shimOutput: true};
AsyncSink.MIDDLEWARE.forEach((fn)=>{fn(ev)});
var ev = { method: "delete", file: ModAPI.util.ustr(args[1]), shim: false, shimOutput: true };
AsyncSink.MIDDLEWARE.forEach((fn) => { fn(ev) });
if (ev.shim) {
return booleanResult(ev.shimOutput);
}
Expand All @@ -140,17 +142,114 @@ ModAPI.meta.credits("By ZXMushroom63");
var result = AsyncSink.fileExists(ModAPI.util.ustr(args[1]));
return booleanResult(result);
}
var ev = {method: "exists", file: ModAPI.util.ustr(args[1]), shim: false, shimOutput: true};
AsyncSink.MIDDLEWARE.forEach((fn)=>{fn(ev)});
var ev = { method: "exists", file: ModAPI.util.ustr(args[1]), shim: false, shimOutput: true };
AsyncSink.MIDDLEWARE.forEach((fn) => { fn(ev) });
if (ev.shim) {
return booleanResult(ev.shimOutput);
}
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!");
}
runtimeComponent();
ModAPI.dedicatedServer.appendCode(runtimeComponent);


async function assureAsyncSinkResources() {
const dec = new TextDecoder("utf-8");
const enc = new TextEncoder("utf-8");
var resourcePackKey = (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");
const objectStore = transaction.objectStore("filesystem");
var object = (await promisifyIDBRequest(objectStore.get(["resourcepacks/manifest.json"])))?.data;
var resourcePackList = object ? JSON.parse(dec.decode(object)) : { resourcePacks: [] };
if (!resourcePackList.resourcePacks.find(x => x.name === "AsyncSinkLib")) {
resourcePackList.resourcePacks.push({
domains: ["minecraft"],
folder: "AsyncSinkLib",
name: "AsyncSinkLib",
timestamp: Date.now()
});
const writeableTransaction = db.transaction(["filesystem"], "readwrite");
const writeableObjectStore = writeableTransaction.objectStore("filesystem");
await promisifyIDBRequest(writeableObjectStore.put({
path: "resourcepacks/manifest.json",
data: enc.encode(JSON.stringify(resourcePackList)).buffer
}));
await promisifyIDBRequest(writeableObjectStore.put({
path: "resourcepacks/AsyncSinkLib/pack.mcmeta",
data: enc.encode(JSON.stringify({
"pack": {
"pack_format": 1,
"description": "AsyncSink Library Resources"
}
})).buffer
}));

var icon = {
path: "resourcepacks/AsyncSinkLib/pack.png",
data: await (await fetch(asyncSinkIcon)).arrayBuffer()
};

const imageTransaction = db.transaction(["filesystem"], "readwrite");
const imageObjectStore = imageTransaction.objectStore("filesystem");

await promisifyIDBRequest(imageObjectStore.put(icon));
}
}

// Client side reminders to enable the AsyncSink Resource Pack
var asyncSinkInstallStatus = false;
var installMessage = document.createElement("span");
installMessage.innerText = "Please enable the AsyncSink resource pack\nIn game, use the .reload_tex command to load textures for modded blocks and items.";
installMessage.style = "background-color: rgba(0,0,0,0.7); color: red; position: fixed; top: 0; left: 0; font-family: sans-serif; pointer-events: none; user-select: none;";
document.body.appendChild(installMessage);

assureAsyncSinkResources();
setInterval(() => {
var resourcePackEntries = ModAPI.mc.mcResourcePackRepository.getRepositoryEntries().getCorrective();
var array = resourcePackEntries.array || [resourcePackEntries.element];
asyncSinkInstallStatus = array.find(x => ModAPI.util.ustr(x.reResourcePack.resourcePackFile.getRef()) === "AsyncSinkLib") ? true : false;
assureAsyncSinkResources();
if (asyncSinkInstallStatus) {
installMessage.style.display = "none";
} else {
installMessage.style.display = "initial";
}
}, 8000);
ModAPI.events.newEvent("custom:asyncsink_reloaded");
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)().then(()=>{
ModAPI.events.callEvent("custom:asyncsink_reloaded", {});
});
}
});
})();
127 changes: 127 additions & 0 deletions examplemods/block_of_steve_advanced.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//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 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 creativeBlockTab = ModAPI.reflect.getClassById("net.minecraft.creativetab.CreativeTabs").staticVariables.tabBlock;
var nmb_BlockSteve = function nmb_BlockSteve() {
blockSuper(this, ModAPI.materials.rock.getRef());
this.$defaultBlockState = this.$blockState.$getBaseState();
this.$setCreativeTab(creativeBlockTab);
}
ModAPI.reflect.prototypeStack(blockClass, nmb_BlockSteve);
nmb_BlockSteve.prototype.$isOpaqueCube = function () {
return 1;
}
nmb_BlockSteve.prototype.$createBlockState = function (t) {
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, //use blockid 198
ModAPI.util.str("steve"),
block_of_steve
);
itemClass.staticMethods.registerItemBlock0.method(block_of_steve);
ModAPI.mc.renderItem.registerBlock(block_of_steve, ModAPI.util.str("steve"));
ModAPI.addEventListener("lib:asyncsink", async () => {
ModAPI.addEventListener("custom:asyncsink_reloaded", ()=>{
ModAPI.mc.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());
});
}
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);
Loading

0 comments on commit a07c320

Please sign in to comment.