Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Scripting APIs undefined #1811

Closed
fineless71 opened this issue Jan 16, 2024 · 10 comments · Fixed by #1878
Closed

[BUG] Scripting APIs undefined #1811

fineless71 opened this issue Jan 16, 2024 · 10 comments · Fixed by #1878
Labels
🐛 bug Something isn't working 👷 development build development build issue 🌑 nextgen
Milestone

Comments

@fineless71
Copy link
Contributor

fineless71 commented Jan 16, 2024

LiquidBounce Branch

Nextgen

LiquidBounce Build/Version

96f3064

Operating System

Linux

Minecraft Version

1.20.4

Describe the bug

Not sure if anyone saw my comment at #1552 (comment), so I'm moving this to an issue:

I couldn't get this fully working. I got some stuff working like adding chat messages (via the client object) and jumping (via mc.player.jump()), but I couldn't get any of the APIs working, even the rotation examples from the comments. For example, api.movementUtil.jump() resulted in the error org.graalvm.polyglot.PolyglotException: TypeError: undefined has no such function "jump"

Steps to reproduce

Try to use one of the scripting APIs, like so:

module.on("enable", function() {
    api.movementUtil.jump()
});

Client Log

[13:23:48] [Render thread/ERROR]: Script caused exception in module REDACTED on enable event!
org.graalvm.polyglot.PolyglotException: TypeError: undefined has no such function "jump"
	at <js>.:anonymous(Unnamed:29) ~[?:?]
	at com.oracle.truffle.polyglot.PolyglotFunctionProxyHandler.invoke(PolyglotFunctionProxyHandler.java:155) ~[liquidbounce.jar:?]
	at jdk.proxy2.$Proxy56.invoke(Unknown Source) ~[?:?]
	at net.ccbluex.liquidbounce.script.bindings.features.JsModule.callEvent(JsModule.kt:85) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.bindings.features.JsModule.callEvent$default(JsModule.kt:83) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.bindings.features.JsModule.enable(JsModule.kt:76) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.features.module.Module$enabled$2.invoke(Module.kt:78) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.features.module.Module$enabled$2.invoke(Module.kt:68) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.config.Value.set(Value.kt:108) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.config.Value.setValue(Value.kt:93) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.features.module.Module.setEnabled(Module.kt:68) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.features.command.commands.client.CommandToggle$createCommand$1.invoke(CommandToggle.kt:52) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.features.command.commands.client.CommandToggle$createCommand$1.invoke(CommandToggle.kt:46) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.features.command.CommandManager.execute(CommandManager.kt:323) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.features.command.CommandExecutor$chatEventHandler$1.invoke(CommandManager.kt:57) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.features.command.CommandExecutor$chatEventHandler$1.invoke(CommandManager.kt:54) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.event.EventManager.callEvent(EventManager.kt:164) ~[liquidbounce.jar:?]
	at net.minecraft.class_408.handler$clj000$liquidbounce$handleChatMessage(class_408.java:1551) ~[client-intermediary.jar:?]
	at net.minecraft.class_408.method_44056(class_408.java) ~[client-intermediary.jar:?]
	at net.minecraft.class_408.method_25404(class_408.java:98) ~[client-intermediary.jar:?]
	at net.minecraft.class_309.method_1454(class_309.java:407) ~[client-intermediary.jar:?]
	at net.minecraft.class_437.method_25412(class_437.java:414) ~[client-intermediary.jar:?]
	at net.minecraft.class_309.method_1466(class_309.java:403) ~[client-intermediary.jar:?]
	at net.minecraft.class_309.method_22678(class_309.java:492) ~[client-intermediary.jar:?]
	at net.minecraft.class_1255.execute(class_1255.java:102) ~[client-intermediary.jar:?]
	at net.minecraft.class_309.redirect$cah000$viafabricplus$storeEvent(class_309.java:1136) ~[client-intermediary.jar:?]
	at net.minecraft.class_309.method_22676(class_309.java:492) ~[client-intermediary.jar:?]
	at org.lwjgl.glfw.GLFWKeyCallbackI.callback(GLFWKeyCallbackI.java:44) ~[lwjgl-glfw-3.3.2.jar:?]
	at org.lwjgl.system.JNI.invokeV(Native Method) ~[lwjgl-3.3.2.jar:?]
	at org.lwjgl.glfw.GLFW.glfwWaitEventsTimeout(GLFW.java:3509) ~[lwjgl-glfw-3.3.2.jar:?]
	at com.mojang.blaze3d.systems.RenderSystem.limitDisplayFPS(RenderSystem.java:238) ~[client-intermediary.jar:?]
	at net.minecraft.class_310.method_1523(class_310.java:1352) ~[client-intermediary.jar:?]
	at net.minecraft.class_310.method_1514(class_310.java:888) ~[client-intermediary.jar:?]
	at net.minecraft.client.main.Main.main(Main.java:265) ~[minecraft-1.20.4-client.jar:?]
	at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:470) ~[fabric-loader-0.15.3.jar:?]
	at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74) ~[fabric-loader-0.15.3.jar:?]
	at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) ~[fabric-loader-0.15.3.jar:?]
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]
	at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
	at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]
	at org.polymc.impl.OneSixLauncher.invokeMain(OneSixLauncher.java:104) ~[NewLaunch.jar:?]
	at org.polymc.impl.OneSixLauncher.launchWithMainClass(OneSixLauncher.java:176) ~[NewLaunch.jar:?]
	at org.polymc.impl.OneSixLauncher.launch(OneSixLauncher.java:186) ~[NewLaunch.jar:?]
	at org.polymc.EntryPoint.listen(EntryPoint.java:144) ~[NewLaunch.jar:?]
	at org.polymc.EntryPoint.main(EntryPoint.java:74) ~[NewLaunch.jar:?]

Screenshots

No response

@github-actions github-actions bot added 🌑 nextgen 🐛 bug Something isn't working 👷 development build development build issue labels Jan 16, 2024
@fineless71 fineless71 changed the title [BUG] Scripting APIs not working [BUG] Scripting APIs undefined Jan 16, 2024
@1zun4
Copy link
Member

1zun4 commented Jan 17, 2024

Looking into it now.

@1zun4 1zun4 closed this as completed in d434d7f Jan 17, 2024
@1zun4
Copy link
Member

1zun4 commented Jan 17, 2024

const script = registerScript({
    name: "MyScript",
    version: "1.0.0",
    authors: ["My Name"]
});

script.registerModule({
    name: "SpeedModule",
    category: "Misc",
    description: "An example module created with LiquidBounce's script API."
}, function (module) {
    module.on("enable", function() {
        client.displayChatMessage("§aHallo - du solltest funktionieren.");
    });

    module.on("disable", function() {
        client.displayChatMessage("§cTschüss - du solltest nicht mehr funktionieren.");
    });

    module.on("playerTick", function(event) {
        if (movementUtil.moving()) {
            movementUtil.strafeWithSpeed(0.3);
        }

        const speed = movementUtil.speed();
        client.displayChatMessage(speed.toString());

        // add velocity
        if (mc.player.onGround) {
            mc.player.jump();
            client.displayChatMessage("jump! :)");
        } else if (mc.player.velocity.y < 0) {
            mc.player.addVelocity(new Vec3d(0.0, 0.0002, 0.0));
        } else {
            mc.player.velocity.y -= 0.1;
        }
    });
});

image

It works well. :)

@fineless71
Copy link
Contributor Author

Thanks, the API is working now. But in your script, the line mc.player.velocity.y -= 0.1; gives me an error:

org.graalvm.polyglot.PolyglotException: TypeError: Cannot read property "y" from undefined
	at <js>.:anonymous(Unnamed:26) ~[?:?]
	at com.oracle.truffle.polyglot.PolyglotFunctionProxyHandler.invoke(PolyglotFunctionProxyHandler.java:155) ~[liquidbounce.jar:?]
	at jdk.proxy2.$Proxy56.invoke(Unknown Source) ~[?:?]
	at net.ccbluex.liquidbounce.script.bindings.features.JsModule.callEvent(JsModule.kt:85) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.bindings.features.JsModule.access$callEvent(JsModule.kt:28) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.bindings.features.JsModule$hookHandler$1.invoke(JsModule.kt:103) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.bindings.features.JsModule$hookHandler$1.invoke(JsModule.kt:98) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.event.EventManager.callEvent(EventManager.kt:170) ~[liquidbounce.jar:?]
	at net.minecraft.class_746.handler$cle001$liquidbounce$hookTickEvent(class_746.java:1396) ~[client-intermediary.jar:?]
	at net.minecraft.class_746.method_5773(class_746.java:218) ~[client-intermediary.jar:?]
	at net.minecraft.class_638.method_18646(class_638.java:1184) ~[client-intermediary.jar:?]
	at net.minecraft.class_1937.method_18472(class_1937.java:492) ~[client-intermediary.jar:?]
	at net.minecraft.class_638.method_32124(class_638.java:260) ~[client-intermediary.jar:?]
	at net.minecraft.class_5574.method_31791(class_5574.java:54) ~[client-intermediary.jar:?]
	at net.minecraft.class_638.method_18116(class_638.java:256) ~[client-intermediary.jar:?]
	at net.minecraft.class_310.method_1574(class_310.java:2011) ~[client-intermediary.jar:?]
	at net.minecraft.class_310.method_1523(class_310.java:1289) ~[client-intermediary.jar:?]
	at net.minecraft.class_310.method_1514(class_310.java:888) ~[client-intermediary.jar:?]
	at net.minecraft.client.main.Main.main(Main.java:265) ~[minecraft-1.20.4-client.jar:?]
	at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:470) ~[fabric-loader-0.15.3.jar:?]
	at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74) ~[fabric-loader-0.15.3.jar:?]
	at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) ~[fabric-loader-0.15.3.jar:?]
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]
	at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
	at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]
	at org.polymc.impl.OneSixLauncher.invokeMain(OneSixLauncher.java:104) ~[NewLaunch.jar:?]
	at org.polymc.impl.OneSixLauncher.launchWithMainClass(OneSixLauncher.java:176) ~[NewLaunch.jar:?]
	at org.polymc.impl.OneSixLauncher.launch(OneSixLauncher.java:186) ~[NewLaunch.jar:?]
	at org.polymc.EntryPoint.listen(EntryPoint.java:144) ~[NewLaunch.jar:?]
	at org.polymc.EntryPoint.main(EntryPoint.java:74) ~[NewLaunch.jar:?]

@fineless71
Copy link
Contributor Author

fineless71 commented Jan 18, 2024

It looks like it worked in your screenshot but for me it seems some things under player is undefined. It doesn't jump for me (nor print the jump message) and client.displayChatMessage(typeof mc.player.onGround); prints undefined. Printing the player itself does give object though. mc.player.jump() it works, I'm not sure why onGround doesn't.

Random guess but maybe it's due to mappings?

@1zun4
Copy link
Member

1zun4 commented Jan 18, 2024

Yes it might be. I actually tested on the dev environment, instead of the release version, so it's likely something with the remapper.

@1zun4 1zun4 reopened this Jan 18, 2024
@1zun4
Copy link
Member

1zun4 commented Jan 18, 2024

It seems the remapper we can access the field of MinecraftClient.player in the remapped name, however after this, we cannot access Entity.onGround, but we can access ClientPlayerEntity.lastOnGround

image

const script = registerScript({
    name: "MyScript",
    version: "1.0.0",
    authors: ["My Name"]
});

script.registerModule({
    name: "SpeedModule",
    category: "Misc",
    description: "An example module created with LiquidBounce's script API."
}, function (module) {
    module.on("enable", function() {
        client.displayChatMessage("§aHallo - du solltest funktionieren.");
        //client.displayChatMessage(JSON.stringify(mc.player));
    });

    module.on("disable", function() {
        client.displayChatMessage("§cTschüss - du solltest nicht mehr funktionieren.");
    });

    module.on("playerTick", function(event) {
        if (movementUtil.moving()) {
            movementUtil.strafeWithSpeed(0.3);
        }

        const speed = movementUtil.speed();
        client.displayChatMessage(speed.toString());

        client.displayChatMessage(typeof mc.player);
        client.displayChatMessage(typeof mc);
        client.displayChatMessage(typeof mc.player.onGround);
        client.displayChatMessage(typeof mc.player.aJ);
        client.displayChatMessage(typeof mc.player.field_5952);
        client.displayChatMessage(typeof mc.player.lastOnGround);
    });
});

I will further debug on what is exactly going on, but since we cannot even access the obfuscated name of it, this means it is likely being remapped, however something else goes wrong.

@1zun4
Copy link
Member

1zun4 commented Jan 18, 2024

So, my guess is the Remapper does not really function as intended. However we can write a better one by injection into Members, which collects all methods and fields of an class and translate there.

@1zun4 1zun4 added this to the 0.1.1 milestone Jan 21, 2024
@1zun4
Copy link
Member

1zun4 commented Jan 25, 2024

It seems the remapper we can access the field of MinecraftClient.player in the remapped name, however after this, we cannot access Entity.onGround, but we can access ClientPlayerEntity.lastOnGround

image

const script = registerScript({
    name: "MyScript",
    version: "1.0.0",
    authors: ["My Name"]
});

script.registerModule({
    name: "SpeedModule",
    category: "Misc",
    description: "An example module created with LiquidBounce's script API."
}, function (module) {
    module.on("enable", function() {
        client.displayChatMessage("§aHallo - du solltest funktionieren.");
        //client.displayChatMessage(JSON.stringify(mc.player));
    });

    module.on("disable", function() {
        client.displayChatMessage("§cTschüss - du solltest nicht mehr funktionieren.");
    });

    module.on("playerTick", function(event) {
        if (movementUtil.moving()) {
            movementUtil.strafeWithSpeed(0.3);
        }

        const speed = movementUtil.speed();
        client.displayChatMessage(speed.toString());

        client.displayChatMessage(typeof mc.player);
        client.displayChatMessage(typeof mc);
        client.displayChatMessage(typeof mc.player.onGround);
        client.displayChatMessage(typeof mc.player.aJ);
        client.displayChatMessage(typeof mc.player.field_5952);
        client.displayChatMessage(typeof mc.player.lastOnGround);
    });
});

I will further debug on what is exactly going on, but since we cannot even access the obfuscated name of it, this means it is likely being remapped, however something else goes wrong.

image

It is now fixed.

@fineless71
Copy link
Contributor Author

Thanks, but I can't use the mapped names with reflection, is this a bug?

// Works
const PlayerMoveC2SPacket = reflectionUtil.classByName("net.minecraft.class_2828");
// Doesn't work, causes error on load
const PlayerMoveC2SPacket = reflectionUtil.classByName("net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket");
org.graalvm.polyglot.PolyglotException: net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket
	at jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[?:?]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:525) ~[?:?]
	at net.fabricmc.loader.impl.launch.knot.KnotClassDelegate.loadClass(KnotClassDelegate.java:226) ~[fabric-loader-0.15.3.jar:?]
	at net.fabricmc.loader.impl.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:119) ~[fabric-loader-0.15.3.jar:?]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:525) ~[?:?]
	at java.lang.Class.forName0(Native Method) ~[?:?]
	at java.lang.Class.forName(Class.java:375) ~[?:?]
	at net.ccbluex.liquidbounce.script.bindings.api.JsReflectionUtil.classByName(JsReflectionUtil.kt:24) ~[liquidbounce.jar:?]
	at <js>.:program(Unnamed:10) ~[?:?]
	at org.graalvm.polyglot.Context.eval(Context.java:429) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.Script.initScript(Script.kt:74) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.ScriptManager.loadScript(ScriptManager.kt:67) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.ScriptManager.loadSafely(ScriptManager.kt:57) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.ScriptManager.loadScripts(ScriptManager.kt:42) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.ScriptManager.reloadScripts(ScriptManager.kt:121) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.CommandScript$createCommand$1.invoke(CommandScript.kt:36) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.script.CommandScript$createCommand$1.invoke(CommandScript.kt:34) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.features.command.CommandManager.execute(CommandManager.kt:322) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.features.command.CommandExecutor$chatEventHandler$1.invoke(CommandManager.kt:59) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.features.command.CommandExecutor$chatEventHandler$1.invoke(CommandManager.kt:56) ~[liquidbounce.jar:?]
	at net.ccbluex.liquidbounce.event.EventManager.callEvent(EventManager.kt:169) ~[liquidbounce.jar:?]
	at net.minecraft.class_408.handler$clk000$liquidbounce$handleChatMessage(class_408.java:1551) ~[client-intermediary.jar:?]
	at net.minecraft.class_408.method_44056(class_408.java) ~[client-intermediary.jar:?]
	at net.minecraft.class_408.method_25404(class_408.java:98) ~[client-intermediary.jar:?]
	at net.minecraft.class_309.method_1454(class_309.java:407) ~[client-intermediary.jar:?]
	at net.minecraft.class_437.method_25412(class_437.java:414) ~[client-intermediary.jar:?]
	at net.minecraft.class_309.method_1466(class_309.java:403) ~[client-intermediary.jar:?]
	at net.minecraft.class_309.method_22678(class_309.java:492) ~[client-intermediary.jar:?]
	at net.minecraft.class_1255.execute(class_1255.java:102) ~[client-intermediary.jar:?]
	at net.minecraft.class_309.redirect$cah000$viafabricplus$storeEvent(class_309.java:1136) ~[client-intermediary.jar:?]
	at net.minecraft.class_309.method_22676(class_309.java:492) ~[client-intermediary.jar:?]
	at org.lwjgl.glfw.GLFWKeyCallbackI.callback(GLFWKeyCallbackI.java:44) ~[lwjgl-glfw-3.3.2.jar:?]
	at org.lwjgl.system.JNI.invokeV(Native Method) ~[lwjgl-3.3.2.jar:?]
	at org.lwjgl.glfw.GLFW.glfwPollEvents(GLFW.java:3438) ~[lwjgl-glfw-3.3.2.jar:?]
	at com.mojang.blaze3d.systems.RenderSystem.pollEvents(RenderSystem.java:202) ~[client-intermediary.jar:?]
	at com.mojang.blaze3d.systems.RenderSystem.flipFrame(RenderSystem.java:220) ~[client-intermediary.jar:?]
	at net.minecraft.class_1041.method_15998(class_1041.java:287) ~[client-intermediary.jar:?]
	at net.minecraft.class_310.method_1523(class_310.java:1349) ~[client-intermediary.jar:?]
	at net.minecraft.class_310.method_1514(class_310.java:888) ~[client-intermediary.jar:?]
	at net.minecraft.client.main.Main.main(Main.java:265) ~[minecraft-1.20.4-client.jar:?]
	at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:470) ~[fabric-loader-0.15.3.jar:?]
	at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74) ~[fabric-loader-0.15.3.jar:?]
	at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) ~[fabric-loader-0.15.3.jar:?]
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]
	at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
	at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]
	at org.polymc.impl.OneSixLauncher.invokeMain(OneSixLauncher.java:104) ~[NewLaunch.jar:?]
	at org.polymc.impl.OneSixLauncher.launchWithMainClass(OneSixLauncher.java:176) ~[NewLaunch.jar:?]
	at org.polymc.impl.OneSixLauncher.launch(OneSixLauncher.java:186) ~[NewLaunch.jar:?]
	at org.polymc.EntryPoint.listen(EntryPoint.java:144) ~[NewLaunch.jar:?]
	at org.polymc.EntryPoint.main(EntryPoint.java:74) ~[NewLaunch.jar:?]

@1zun4
Copy link
Member

1zun4 commented Jan 27, 2024

Oh, I missed this! Please create another issue, since this was closed now. Yes, I forgot about remapping the input of the reflection util.

Otherwise use the GraalJS Java.type(...) function.

var BigDecimal = Java.type('java.math.BigDecimal');
var point1 = new BigDecimal("0.1");
var two = BigDecimal.TWO;
console.log(point1.multiply(two).toString());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 bug Something isn't working 👷 development build development build issue 🌑 nextgen
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants