Skip to content

Rewrite Adventure Book calls to BookMeta#14081

Open
TheosRee wants to merge 2 commits into
PaperMC:mainfrom
TheosRee:fix/book
Open

Rewrite Adventure Book calls to BookMeta#14081
TheosRee wants to merge 2 commits into
PaperMC:mainfrom
TheosRee:fix/book

Conversation

@TheosRee

@TheosRee TheosRee commented Jul 19, 2026

Copy link
Copy Markdown

Fixes #14030 with simply re-routing all calls to the BookMeta, which now itself has (or inherits) the relevant methods.

I have to admit that I think that could break some stuff when it does want to access it and research how to determine that in a better way than just re-rerouting everything (like checking just calls from BookMeta to Book like mentioned in the issue). I now understand how that works and check the owner for BookMeta and change just the return types to also BookMeta.

@TheosRee
TheosRee requested a review from a team as a code owner July 19, 2026 01:13
@github-project-automation github-project-automation Bot moved this to Awaiting review in Paper PR Queue Jul 19, 2026
@TheosRee
TheosRee marked this pull request as draft July 19, 2026 01:44
@TheosRee
TheosRee marked this pull request as ready for review July 19, 2026 15:29
@TheosRee

Copy link
Copy Markdown
Author

I would like to know if the version check should be dropped (the method with a different signature can't be compiled against 26.2 anyways) and if the added code should be at the start or end of the method. I tried to figure it from the changes but it did not lead me to a fixed result.

I also hope that the removal of the remnant method is ok and if I should also remove the methods which now just call super.

@masmc05

masmc05 commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Wouldn't this break if

  1. it was used in a chain, so bookMeta.method1().method2() would have had a Book#method2() signature and not a BookMeta#method2(), method1's rewrite to return bookmeta instead of book will break method2
  2. The result is passed into a non api method that accepts only an adventure book, and it would result in some error as book meta isn't a book anymore

@TheosRee

Copy link
Copy Markdown
Author
  1. Yes, any chained call after one of the pages setter methods will break with a java.lang.IncompatibleClassChangeError: Class org.bukkit.craftbukkit.inventory.CraftMetaBookSigned does not implement the requested interface net.kyori.adventure.inventory.Book. When I am correct that can't be fixed easily because that is an independent call to a different interface? Changing that would also rewrite all "legit" calls to adventure books. Having that method at the end of the chain works however since all other methods (author and title) return the BookMeta and not the Book.
  2. That is nothing that I thought of but like my thoughts to 1.: With the openBook method on the player that is kinda legit, but you could also pass the item stack when you work with meta anyways - without you would just work with a Book directly?
Here is my test with the branch:
@Override
public boolean execute(final CommandSender sender, final String commandLabel, final String[] args) {
    final ItemStack book = ItemStack.of(Material.WRITTEN_BOOK);
    final BookMeta meta = (BookMeta) book.getItemMeta();
    eval("getter", () -> meta.pages());
    eval("workingChain", () -> meta.author(Component.empty()).title(Component.empty()).pages(List.of()));
    eval("setterL", () -> meta.pages(List.of()));
    eval("setterL2", () -> meta.pages(List.of()).pages());
    eval("setterA", () -> meta.pages(Component.empty()));
    eval("setterA2", () -> meta.pages(Component.empty()).author());
    eval("doubleSetter", () -> meta.pages(Component.empty()).pages(Component.empty()));
    eval("doubleSetter2", () -> meta.pages(List.of()).pages(List.of()));
    eval("title", () -> meta.title(Component.empty()).pages(List.of()));
    eval("title2", () -> meta.title(Component.empty()).pages(List.of()).title());
    eval("author", () -> meta.author(Component.empty()).pages(List.of()));
    eval("author2", () -> meta.author(Component.empty()).pages(List.of()).author());
    sender.sendMessage(Component.text("Executed book meta test"));
    return true;
}

private void eval(final String name, final Runnable runnable) {
    try {
        runnable.run();
        getLogger().info(name + " executed");
    } catch (final Error error) {
        getLogger().severe(name + " failed: " + error.getMessage());
    }
}

bookmetatest
getter executed
workingChain executed
setterL executed
setterL2 failed: Class org.bukkit.craftbukkit.inventory.CraftMetaBookSigned does not implement the requested interface net.kyori.adventure.inventory.Book
setterA executed
setterA2 failed: Class org.bukkit.craftbukkit.inventory.CraftMetaBookSigned does not implement the requested interface net.kyori.adventure.inventory.Book
doubleSetter failed: Class org.bukkit.craftbukkit.inventory.CraftMetaBookSigned does not implement the requested interface net.kyori.adventure.inventory.Book
doubleSetter2 failed: Class org.bukkit.craftbukkit.inventory.CraftMetaBookSigned does not implement the requested interface net.kyori.adventure.inventory.Book
title executed
title2 failed: Class org.bukkit.craftbukkit.inventory.CraftMetaBookSigned does not implement the requested interface net.kyori.adventure.inventory.Book
author executed
author2 failed: Class org.bukkit.craftbukkit.inventory.CraftMetaBookSigned does not implement the requested interface net.kyori.adventure.inventory.Book
Executed book meta test

@masmc05

masmc05 commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Yeah those are harder to fix, that's why in the original issue I mentioned redirecting instead the calls to an internal class with static methods that take the book meta + the arguments and returns an adventure book, this way nothing further should be affected as it's continuing to expect the Book instance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Awaiting review

Development

Successfully merging this pull request may close these issues.

NoSuchMethodError for (removed adventure) Book interface's setter

2 participants