Advice on mapping CraftBukkit to Paper / Bukkit classes

Bletch

New member
Jan 8, 2022
4
0
1
tl;dr Is there a simple way (I know, I know!) to map the underlying CraftBukkit classes to those documented in the Paper API?

I'm running against 1.20.4 so I think I might be falling victim of the issues discussed here:


I feel that this might be a holy grail type question, so please forgive my ignorance if this is either obvious or impossible - or both!

Also I'm not sure of the correct terminology when describing the differences I see between the Paper API that I interact with and the CraftBukkit classes that I get returned. So, I've decided to refer to these as Paper and CraftBukkit below.

What am I trying to do?

I am developing a plugin that creates a bridge between Python and the Paper server. The goal is that most / all of the Paper server objects will be accessible to Python developers using a Paper API / Java-like syntax.

For example:

Python:
player_location = current_player.getLocation()
block_at_player_location = player_location.getBlock()
log = Material.ACACIA_LOG
block_at_player_location.setType(log)
block_data_at_player_location = block_at_player_location.getBlockData()

This will be teaching aid - I work in UK Schools and work with (often autistic) kids and my bridge will primarily be used by them. Performance is also not a massive factor for my implementation either.

I'm using reflection and when interrogating the result of a method call I 'sometimes' get CraftBukkit classes instead of the Paper ones I'm expecting.

It’s critical for me to be able to map these CraftBukkit classes to the relevant Paper class that I can see in the documentation. Being able to identify the returned class and matching it to what I'm expecting in the documentation allows me instantiate the relevant Python class to wrap the returned object.

Knowing the correct class also allows me to get the methods and fields for that class/interface as well as any parameters and return types. This is essential to allow me to cast from Python to Java data types and back again.

As a simple example, given the interface class:

org.bukkit.craftbukkit.v1_20_R3.block.impl.CraftRotatable

...which might be returned from a call to get BlockData on a Block of type Material.ACACIA_LOG.

I need to know that this CraftBukkit interface class is “equivalent” to the BlockData interface in the Paper world named:

org.bukkit.block.data.Orientable

Ideally I would like a list of all such maps between CraftBukkit classes / interfaces / objects / Enums and Paper objects. ASSUMING, that is, that they have a one-to-one, identical purpose relationship.

The alternative

I THINK I can create such a mapping myself but it would involve me
  • writing code to create "stuff" in the Minecraft world using a known Paper class / interface
  • interrogating the classes that actually get returned
  • outputting the mapping to a log file
  • copying the output into code structures

I'd then use that in Python to create a manual mapping. But I currently don't know what I don't know - if you see what I mean. What classes are likely to be CraftBukkit and what are currently Paper?

I've written a small Python-side script to achieve this for BlockData. Here's a sample of the map between Paper and CrafftBukkit BlockData interface classes.

Note I've normalised the CraftBukkit interface class by removing the version number.

Python:
block_data_class_map = {
    'org.bukkit.block.data.type.Switch': 'org.bukkit.craftbukkit.block.impl.CraftButtonAbstract',
    'org.bukkit.block.data.type.Door': 'org.bukkit.craftbukkit.block.impl.CraftDoor',
    'org.bukkit.block.data.type.Fence': 'org.bukkit.craftbukkit.block.impl.CraftFence',
    'org.bukkit.block.data.type.Gate': 'org.bukkit.craftbukkit.block.impl.CraftFenceGate',
    'org.bukkit.block.data.type.HangingSign': 'org.bukkit.craftbukkit.block.impl.CraftCeilingHangingSign',
    'org.bukkit.block.data.type.Leaves': 'org.bukkit.craftbukkit.block.impl.CraftLeaves',
    'org.bukkit.block.data.Orientable': 'org.bukkit.craftbukkit.block.impl.CraftRotatable',
    ...
    ...
    ...
    'org.bukkit.block.data.type.TurtleEgg': 'org.bukkit.craftbukkit.block.impl.CraftTurtleEgg',
}

I'd really like to avoid this approach if I can. Or is this my only sensible option?

Any help, advice or pointers gratefully appreciated!

Thanks for your time.
 
Version Output
This server is running Paper version git-Paper-389 (MC: 1.20.4) (Implementing API version 1.20.4-R0.1-SNAPSHOT) (Git: 848a396)
You are 20 version(s) behind
Download the new version at: https://papermc.io/downloads/paper

stefvanschie

Moderator
Staff member
Dec 17, 2021
92
3
16
8
In general, the types you access from the API are interfaces, which are implemented by the types in CraftBukkit. Taking your example of CraftRotatable to Orientable, CraftRotatable looks as follows:
Java:
public final class CraftRotatable extends org.bukkit.craftbukkit.block.data.CraftBlockData implements org.bukkit.block.data.Orientable { /* code */ }
Note that the relevant API class here is the interface that is being implemented. Hence, if you get a CraftBukkit type such as CraftRotatable the "only" thing you need to do is scan the JAR, or something else that contains the code, for CraftRotatable and then find the interface it is implementing, which in this case would give you Orientable.

Now, there are some classes that are defined completely in the API, so these don't need any conversion, but it should be easy to skip those.

Again, this conversion is in general, it could be that there are some CraftBukkit types that might give you some trouble, but I think that if you were to use the approach I mentioned, you should get the right API type in the majority of the cases. If you encounter a more complicated case, defining those by hand is probably the easiest way to fix those.
 
  • Like
Reactions: Bletch

Bletch

New member
Jan 8, 2022
4
0
1
Thanks for taking the time to reply, stefvanschie.

Note that the relevant API class here is the interface that is being implemented. Hence, if you get a CraftBukkit type such as CraftRotatable the "only" thing you need to do is scan the JAR, or something else that contains the code, for CraftRotatable and then find the interface it is implementing, which in this case would give you Orientable.

Yeah, I think the problem here is that I am coming at the problem from the Paper API and not the other way around. I'm 'happy' to go with a trial and error approach if it's my only option. I just didn't want to go through such an effort only to be pointed towards an existing list of relationships some months later. o_O

If you have a moment, can I run a couple of specific questions past you?

1) Might it be better for me to be driven by the CraftBukkit source / documentation instead of the Paper version ?
2) If not, then can I run a suggested workflow past you? See below.

Background / Current Approach​


Currently when I create my Python objects I am doing something like this:

Python:
class OrientableBlockData(BlockDataInterface):
    _JAVA_CLASS = "org.bukkit.block.data.Orientable"

This tells my Object Manager that if a class type of org.bukkit.block.data.Orientable is returned, then I should wrap the object in a Python class of OrientableBlockData and return it.

Obviously, as we've discussed I don't get the class because it is simply an interface implemented by certain Craft**** classes.

I still need to know this interface class because interrogating it is what will tell me what fields, methods, parameters and return types I can expect - and it's documented in the Paper API.

So my code fails when I encounter a Craft**** class because my Object Manger cannot match the returned class type to any of the Python classes that have registered with it.

To accommodate this I currently do the following.

Python:
class OrientableBlockData(BlockDataInterface):
    _JAVA_CLASS = "org.bukkit.block.data.Orientable"
    _AKA = "org.bukkit.craftbukkit.block.impl.CraftRotatable" # Normalised to exclude version number

This seems to work - aside from the ball-ache of building the map.

Perhaps I can retrieve the corresponding Paper interface by calling getInterfaces()?​


Interestingly, whilst building the BlockData map I found that I can get the Paper interface class that the CraftBukkit-implementing class returns by calling getInterfaces() on the CraftBukkit class. Perhaps this will work for all such implementing classes?

For reference, here's a snippet of Python code that does that. I think it maps directly on to the same logic in Java.

Python:
from bletchcraft import World
from bletchcraft.reflection.block_data import BlockDataInterface
from bletchcraft.reflection.bom import BletchCraftObjectManager
from bletchcraft.reflection.enums.material import Material

world: World = World()
bom: BletchCraftObjectManager = BletchCraftObjectManager(world)

bukkit = bom.bukkit

for material in list(Material):
    # Assume that this CraftBukkit class has no interfaces - hence no Paper class
    paper_class = None

    # Does this Material have BlockData? Or is this a Material instance that has no BlockData?
    block_data = bukkit.createBlockData(material)

    if block_data:
        block_data_class = block_data.getClass()

        # Do we have interfaces?
        interfaces = block_data_class.getInterfaces()
        if interfaces:
            paper_class = interfaces[0].getClass().getName()

    # paper_class now either contains the name of the org.bukkit.block.data.BlockData interface or None
    ...
    ...
    ...

The assumption in this code is that the first interface is either the sole BlockData interface or it's a BlockData type - i.e. extends other BlockData interfaces.

Suggested Workflow​

I'm sure there will be exceptions to this approach but, based on your advice, this is my current thinking:
  1. If possible, act on a discrete / self-contained part of the Paper API before moving on to the next - otherwise unexpected class returns coming from multiple parts of the API will make this hellishly difficult to debug
  2. Review the Paper API and tool up to expect a Paper class but be ready for an interface
  3. Write some test code
  4. Examine the class of what is returned
  5. IF the return class is a Craft**** type, then
    1. Call getInterfaces() on it to see if I can match the Craft**** class to a Paper interface found in the documentation.
    2. OR search the CraftBukkit source code for that Craft**** class and see what Paper (org.bukkit...) class it implements
  6. Find the Paper interface that is implemented by the Craft**** class
  7. Tell my Python class that
    1. The _JAVA_CLASS is equal to the Paper interface class name
    2. The _AKA is set to the class I establised in 5)
  8. Re-test the code and loop to 4 if it still doesn't work
I'm not sure how to "scan the Jar" (unzip and decompile the .class source?), but I guess that is a replacement for the CraftBukkit source in 5.2) above?

Does this sound like a logical way to proceed?

Again, thank you for your time. It really is very much appreciated.
 

stefvanschie

Moderator
Staff member
Dec 17, 2021
92
3
16
8
Your suggested workflow sounds good, yes. As for scanning the JAR, that's just an option, you can indeed also read the source code directly. As long as you can somehow find the CraftBukkit class and figure out which interface it implements, you'll be fine.
 
  • Like
Reactions: Bletch