WesJD / AnvilGUI

Capture user input in Minecraft through an anvil GUI in under 20 lines of code

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

AnvilGUI doesn't work for bedrock players

lucasfrederico opened this issue · comments

AnvilGUI does not display correctly for bedrock players.

I can't click on the result item because it says "Enchantment Cost: 1"

In the image below, on the left is the Bedrock player's view and on the right is the Java player's view.
image

I'm using version 1.9.4-SNAPSHOT of AnvilGUI and the code is this:

        new AnvilGUI.Builder()
                    .plugin(LFAuctionHousePlugin.getInstance())
                    .title("Starting bid:")
                    .itemLeft(new ItemStack(Material.PAPER))
                    .onClose(stateSnapshot -> TaskUtil.runSyncSafe(() -> inventoryController.inventoryFactory.getAHSellInventory(new AHSellInventory(data, profile, guild)).open(stateSnapshot.getPlayer())))
                    .text("0")
                    .onClick((slot, stateSnapshot) -> {
                        var localPlayer = stateSnapshot.getPlayer();
                        var response = stateSnapshot.getText();

                        localPlayer.sendMessage(response);

                        if (response.isEmpty()) {
                            TaskUtil.runSyncSafe(() -> inventoryController.inventoryFactory.getAHSellInventory(new AHSellInventory(data, profile, guild)).open(localPlayer));
                            return AnvilGUI.Response.close();
                        }

                        try {
                            var price = BigDecimal.valueOf(MathUtil.parseSuffixedCurrency(response));

                            if (itemController.isDeniedItemPrice(player, price)) {
                                return AnvilGUI.Response.close();
                            }

                            data.setPrice(price);
                            TaskUtil.runTask(() -> inventoryController.inventoryFactory.getAHSellInventory(new AHSellInventory(data, profile, guild)).open(localPlayer));
                        } catch (Exception e) {
                            UtilMessages.sendMessage(player, PREFIX, false, "Invalid number!");
                        }

                        return AnvilGUI.Response.close();
                    })
                    .open(player);

I don't have experience in working with bedrock - is the enchantment level in the bedrock version changeable at all? Also it doesn't seem to update the anvil title + input value. Either the relevant packets are not translated or bedrock doesn't have the same packets.
Is the title of a window even changeable in bedrock? The open window packet is missing the title property: bedrock, java.

I assume you are using Geyser as a proxy for Bedrock players? As hinted at by @mastercake10, I think this is an issue with Geyser rather than AnvilGUI. The Bedrock Edition client has many differences from the Java Edition client that makes it basically impossible to provide 100% compatibility.

From what I can tell based on past conversations in the GeyserMC Discord community, it seems like the enchantment cost for renaming items is hard-coded by Mojang to always be 1 level in the Bedrock Edition client.

Either way I would reach out to the GeyserMC community or open an issue in their repository instead, I don't think there's anything AnvilGUI can do to change the rename enchantment cost on Bedrock Edition.

Also, you shouldn't really need to use AnvilGUI on Bedrock Edition since the Bedrock Edition client has support for custom forms with text input. Geyser has an API for it.

@0dinD yes, this is exactly what I was thinking. Maybe we can temporally increase the player's level (send a packet), then catch the anvil response packet and don't decrease the player's level?

Another option would be using an alternative approach, like a sign for input. I used to do that in my plugins, but its out of scope for this lib.

Also, you shouldn't really need to use AnvilGUI on Bedrock Edition since the Bedrock Edition client has support for custom forms with text input. Geyser has an API for it.

Wow, this is even better. Haven't heard about it since I never played bedrock.

yes, this is exactly what I was thinking. Maybe we can temporally increase the player's level (send a packet), then catch the anvil response packet and don't decrease the player's level?

I thought about that too but I'm not sure how I feel about it. It think it could possibly cause unwanted side-effects. Then again, I'm not opposed to having it as an option, where the plugin author who uses AnvilGUI can choose the behavior.

But also, I'm not sure if it's wise for AnvilGUI to start providing support for Bedrock Edition (who will test it?). Like, we already struggle to maintain it for Java Edition, so should we really increase the maintenance burden even more?

I think both the level increase and the Bedrock Forms solution could be implemented by the plugin which uses AnvilGUI instead. In fact, someone could make a wrapper library for it. The Geyser API can be used to determine whether someone is using the Bedrock Edition client or not. It could look something like this, in terms of pseudocode:

if (isBedrock) {
    showGuiUsingBedrockForms();
} else {
    showGuiUsingAnvilGui();
}

I'm a little curious if the packet option would work, but otherwise agree that anything beyond that feels out of scope for this library. Would that be https://wiki.vg/Protocol#Set_Experience ?

I'm a little curious if the packet option would work

Well I'm not arguing that it wouldn't work, I'm just saying there could be unwanted side-effects (e.g. level-up sounds being played and what not). Which might be fine for some, but most people are probably just using Java Edition and may not want these unnecessary side-effects. But my main point is that it feels weird to shoehorn this into AnvilGUI when it could just as easily be done as a separate library (and better yet, with Bedrock Forms). It will just become yet another thing that has to be maintained for each version, which doesn't seem like a good idea right now.

But either way the decision is not up to me, I'm just voicing my opinion for the sake of discussion. I don't want to discourage anyone from trying the XP packet solution, because I do think it could work, I just don't think it seems like the best solution.

Alright, I see how that could be annoying, but if it can be done without side-effects, my opinion is it would add some nice value here. I understand overhead is an issue, but it is just a single packet.

It looks like the packet has Level and Total Experience fields - it could be that merely setting the Level doesn't trigger the sound. I'll check this and get back to you.

I don't think Bedrock Forms can do Anvil? At least from what I've read.

Alright, I see how that could be annoying, but if it can be done without side-effects, my opinion is it would add some nice value here. I understand overhead is an issue, but it is just a single packet.

It looks like the packet has Level and Total Experience fields - it could be that merely setting the Level doesn't trigger the sound. I'll check this and get back to you.

I don't think Bedrock Forms can do Anvil? At least from what I've read.

Yeah, this is the best option without the need to maintain the bedrock form thingy. This is an Anvil GUI after all, and not an input library. The set experience packet doesn't seem to play a sound, for example the command /minecraft:experience set @p 10 levels is silent (at least in the java edition, I don't have bedrock)

Yeah it's entirely possible that the level-up sound is handled by the server sending a separate packet. It was just an obvious example of one possible side-effect. I haven't actually dug into the Java Edition client source code, so perhaps my concerns are unwarranted.

Yeah, this is the best option without the need to maintain the bedrock form thingy.

To me it seems like it would be much easier to maintain a wrapper library that uses AnvilGUI for Java Edition clients and Bedrock Forms for Bedrock Edition clients rather than to mess with packets/NMS. Because that solution would be based solely on APIs (AnvilGUI and the Geyser/Cumulus API), and so once written, it would require minimal maintenance. The packet-based solution is much more likely to break when Mojang/Spigot/Paper etc. change the internals and will require even more version-specific NMS code if we go that route.

Also, to clarify, I do not mean that AnvilGUI should maintain the Bedrock Forms wrapper library. The entire point is that it could just as well be maintained by someone else as a separate and optional library (so as to split the maintenance burden).

I don't think Bedrock Forms can do Anvil? At least from what I've read.

This is an Anvil GUI after all, and not an input library.

OH, yeah sorry, I kind of forgot about that. To me AnvilGUI is just a modal text input library (with the whole "anvil" thing being an implementation detail). But using it for custom anvil recipes and stuff like that is also a fair point, and for that, Bedrock Forms obviously won't help.

Then again, I think modal text input is the most common use-case for AnvilGUI (that's also what the author of this issue seems to be using it for), and for that, I still think the wrapper library with Bedrock Forms is a much better solution.

And if you are using AnvilGUI for anything else, I feel like you wouldn't usually set the repair cost to 0 anyway? It seems like a niche use-case to me, but of course I could be totally wrong about that.


So yes, I do agree that the packet-based solution could add a little value for someone (just not sure who that someone is yet). And if it can be done without side-effects, without too much of an additional maintenance burden, then that sounds great! I won't argue against that.

Yeah it's entirely possible that the level-up sound is handled by the server sending a separate packet. It was just an obvious example of one possible side-effect. I haven't actually dug into the Java Edition client source code, so perhaps my concerns are unwarranted.

I couldn't say as far as the sound, but I just tried this out with Geyser/Floodgate 1.20.6 and 1.21 (dev builds), and there were no noticeable problems. Opted to just use ProtocolLib as it was quicker:

ProtocolManager manager = ProtocolLibrary.getProtocolManager();
PacketContainer exp = new PacketContainer(PacketType.Play.Server.EXPERIENCE);
exp.getIntegers().write(1, 20);
manager.sendServerPacket((Player)conversable, exp);

The client's experience was visibly set to 20 and reverted to its actual value on relog (I did not try setting it back with a second packet, which I imagine we'd want to). No sounds to be heard, and the GUI appeared to work as expected on both Java and Bedrock. I also confirmed what OP claims—the Bedrock GUI does not work at 0 exp level.

It sounds like we just need to decide how we'd want this to look. Are we thinking something like .geyser-compat(true) for the Builder, or should we just do it automatically?

I think the users of my plugin can also benefit from this. Sending a packet when opening the Anvil and sending another one to reset the level after closing the GUI sounds viable. We just need to check if the player joined through bedrock. I think we should enable geyser support by default, since this is what I would expect (a working anvil gui on bedrock).

I think a setting like geyser-compat would be good if the packets are also sent for Java Edition clients, so that users/developers can opt out of the compatibility to avoid side-effects. Unless we can prove that there are no side-effects. The level indicator being changed is technically a side-effect that Java Edition users might not want (although admittedly, it's a pretty small thing, so not sure if it really counts).

But yeah, if we use the Geyser API to check whether the player is using a Java or Bedrock Edition client and then only apply the workaround for Bedrock Edition clients, then I think we should just enable it by default since Java Edition clients will be unaffected and Bedrock Edition clients have no better option. So I think this seems like the best way to do it.

Although I didn't observe any side effects, it's not out-of-the-question that future versions of Minecraft might introduce some. So it sounds like we want geyser-compat, enabled by default, provided Geyser is installed, and then only apply it if the client is Bedrock. I should be able to get started on this next week.