Skip to content
Aki edited this page Jul 27, 2018 · 8 revisions

Linkstone aims to support plugins that depend on internal implementation classes of CraftBukkit. These classes are in the package net.minecraft.server and org.bukkit.craftbukkit, often referred as NMS and OBC. To achieve this goal, linkstone provides code that has the exact same names as NMS and OBC but calls Glowstone implementations instead.

Writing those classes is simplified by multiple annotations. The most important ones are @LClassfile, @LMethod, @LConstructor and @LField. Classes, methods, constructors and fields that exist in NMS/OBC code must be annotated with those. If you use one of those annotations, you'll have to define the CraftBukkit version that you targeted while writing the code.

We'll use the @LMethod annotation as a showcase. The principals below do also apply to @LClassfile, @LConstructor and @LField.

import static net.glowstone.linkstone.annotations.Version.*;
import net.glowstone.linkstone.annotations.*;
// This method, with this behavior exists in CraftBukkit 1.11 R1 and 1.12 R1
@LMethod(version = { V1_11_R1, V1_12_R1 })
public int getViewDistance() {
    return glowPlayer.getViewDistance();
}

// This example method exists only in CraftBukkit 1.12 R1.
// If we compile linkstone for version 1.11, this method will not be present.
@LMethod(version = V1_12_R1)
public boolean isSpectator() {
    return glowPlayer.isSpectator();
}

What about obfuscation?

A lot of methods and fields in CraftBukkit have not been deobfuscated. They have random names like a or b. Instead of assigning these strange names to our handwritten methods we can use the annotations. They allow us to obfuscated methods:

// If we compile linkstone for version 1.11 this method method will be named "a".
// When compiling linkstone for version 1.12 it's named "b".
@LMethod(version = V1_11_R1, name = "a")
@LMethod(version = V1_12_R1, name = "b")
public int getViewDistance() {
    return glowPlayer.getViewDistance();
}

Multiple versions, multiple behaviors?

The behavior of a method might change between multiple CraftBukkit versions.

Let's say we got a method that calculates the distance between two entitys. In CraftBukkit 1.11 it might have returned the distance while it returns the squared distance in CraftBukkit 1.12.

We could express this as follows:

// This method exists only for CraftBukkit 1.11.
// It returns the distance.
@LMethod(version = V_11_R1)
public int getDistance(Entity that) {
    int xdiff = this.x - that.x;
    int ydiff = this.y - that.y;
    int zdiff = this.z - that.z;
    return Math.sqrt(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff);
}

// This method exists for Craftbukkit 1.12 and returns the squared distance.
// If we do also call it "getDistance" we will get a compilation error so we had to rename it.
// To fix that, we assign the correct name in the annotation.
@LMethod(version = V_12_R1, name = "getDistance")
public int getDistance_v12(Entity that) {
    int xdiff = this.x - that.x;
    int ydiff = this.y - that.y;
    int zdiff = this.z - that.z;
    return xdiff * xdiff + ydiff * ydiff + zdiff * zdiff;
}

Overriding annotated methods

What if we override an annotated method? Therefore we got the @LOverride method. The compiler ensures that the overridden method has a @LMethod annotation and applies it to the overriding method.

Here's an example where the @LMethod annotation will also be applied to the overriding method. Both methods are present in version 1.12 and will be renamed to "z".

public class EntityHuman {
    @LMethod(version = V1_12_R1, name = "z")
    public void isCreative() {
        return false;
    }
}
public class EntityPlayer extends EntityHuman {
    private GlowEntity glow;

    @LOverride
    public boolean isCreative() {
        return glow.isCreative();
    }
}