Skip to content

Waypoint

Waypoints

The External Waypoint Packet Listener is a feature that allows the server to send waypoints to the client. This is useful for server admins who want to provide waypoints to players without requiring an addon mod.

Examples

Below is an example of what the packet json looks like and what information the server sends to the client to process and interpret.

Packet Identifier

discriminator:0
channel:journeymap:waypoint

Packet Buffer waypoint StringUtf value -> waypointjson.toString(); action StringUtf value-> "create" or "delete" announce Boolean -> value true or false : Announce determines if the user is notified when a waypoint is created or deleted.

Packet Reader (journeymap's client code)

this.waypoint = buf.readUtf();
this.action = buf.readUtf();
this.announce = buf.readBoolean();

Example Create Payload

{
  "id": "home_-10,70,32",
  "name": "home",
  "icon": "journeymap:ui/img/waypoint-icon.png",
  "enable": true,
  "type": "Normal",
  "origin": "external",
  "x": -10,
  "y": 70,
  "z": 32,
  "r": 85,
  "g": 255,
  "b": 255,
  "persistent": false,
  "dimensions": [
    "minecraft:overworld"
  ]
}

id -> id = "name + "_" + x + "," + y + "," + z" this format is required.
name -> waypoint name
icon -> journeymap:ui/img/waypoint-icon.png this is required
enable -> required to be true
type -> Normal or Death
origin -> external or external-force external-force cannot be disabled/hidden on the client
x -> x coord
y -> y coord
z -> z coord
r -> red value
g -> green value
b -> blue value
persistent -> true saves to disk, will load when the user restarts minecraft, or changes dimensions. false is not saved to disk, is removed from memory when the user exists minecraft
dimensions -> list of dimensions

Example Delete Payload

{
  "name": "test"
}

name -> waypoint name is all that is required to delete the waypoint. Example implementation using CraftBukkit

    public static void createWaypoint(Player player, String name, IntVector3 pos, WaypointType type, ChatColor color) {
        Color rgb = color.getColor();
        JsonObject obj = new JsonObject();
        obj.addProperty("id", name + '_' + pos.getX() + ',' + pos.getY() + ',' + pos.getZ());
        obj.addProperty("name", name);
        obj.addProperty("icon", "waypoint-normal.png");
        obj.addProperty("enable", true);
        obj.addProperty("type", type.type);
        obj.addProperty("origin", "external");
        obj.addProperty("x", pos.getX());
        obj.addProperty("y", pos.getY());
        obj.addProperty("z", pos.getZ());
        obj.addProperty("r", rgb.getRed());
        obj.addProperty("g", rgb.getGreen());
        obj.addProperty("b", rgb.getBlue());
        obj.addProperty("persistent", false);

        JsonArray dimensions = new JsonArray(1);
        dimensions.add("minecraft:" + player.getWorld().getName());
        obj.add("dimensions", dimensions);

        PacketDataSerializer out = new PacketDataSerializer(Unpooled.buffer());
        out.writeByte(0); // Extra Byte for Forge
        out.a(obj.toString()); // Payload
        out.a(CREATE); // Action
        out.writeBoolean(false); // Announce
        player.sendPacket(new PacketPlayOutCustomPayload(WAYPOINT_KEY, out));
    }

    public static void deleteWaypoint(Player player, String name) {
        JsonObject obj = new JsonObject();
        obj.addProperty("name", name);
        obj.addProperty("origin", "external");

        PacketDataSerializer out = new PacketDataSerializer(Unpooled.buffer());
        out.writeByte(0); // Extra Byte for Forge
        out.a(obj.toString()); // Payload
        out.a(DELETE); // Action
        out.writeBoolean(false); // Announce
        player.sendPacket(new PacketPlayOutCustomPayload(WAYPOINT_KEY, out));
    }

If you have the PacketDataSerializer (PacketBuffer) deobfuscated it would most likely use buffer.writeString(...) rather than buffer.a(...) If you want to use a different buffer/write manually the internals of that method are something like:

    public PacketDataSerializer writeString(String s, int byteLimit) {

        byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
        if (bytes.length > byteLimit) {
            throw new EncoderException("String too big (was " + bytes.length + " bytes encoded, max " + byteLimit + ")");
        } else {

            this.writeVarInt(bytes.length);
            this.writeBytes(bytes);
            return this;
        }
    }