Skip to content
Crypto Morin edited this page Jun 11, 2021 · 7 revisions

Welcome to the XSeries wiki!

Most of the examples and usages are explained in the JavaDocs. I'm just leaving some other methods that are used for testing and generating purposes here.

Testing some important materials that can cause issues:

public static void main(String[] args) {
    XMaterial[] subjects = {XMaterial.MELON, XMaterial.MELON_SLICE, XMaterial.CARROT, XMaterial.CARROTS,
            XMaterial.MAP, XMaterial.FILLED_MAP, XMaterial.BLACK_GLAZED_TERRACOTTA, XMaterial.COD_BUCKET, XMaterial.WHITE_DYE};

    for (XMaterial subject : subjects) {
        Material parsed = subject.parseMaterial().orElse(null);
        Material suggestion = subject.parseMaterial(true).orElse(null);

        print("Matched(" + subject.name() + ") -> " + XMaterial.matchXMaterial(subject.name()) +
                ", parsed: " + parsed + ", suggestion: " + suggestion);
    }
}

Convert a Bukkit/XItemStack serialized file to new materials

public static void convertYAMLMaterial(File file) {
    StringBuilder sb = new StringBuilder();

    try {
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.trim().startsWith("type:")) sb.append(line);
                else {
                    int index = line.indexOf(':');
                    String material = line.substring(index + 1);
                    XMaterial mat = XMaterial.matchXMaterial(material).orElse(null);
                    if (mat == null || mat.name().contains(mat.parseMaterial().orElse(null).name()) || mat.parseMaterial().orElse(null).name().contains(mat.name())) {
                        sb.append(line).append(System.lineSeparator());
                        continue;
                    }
                    sb.append(line, 0, index).append(": ").append(mat.parseMaterial().orElse(null).name());
                    if (!XMaterial.isNewVersion() && mat.getData() != 0) {
                        sb.append(System.lineSeparator());
                        sb.append(line, 0, index - 4).append("damage: ").append(mat.getData());
                    }
                }
                sb.append(System.lineSeparator());
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    try {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
            writer.write(sb.toString());
            writer.flush();
        }
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

Enum differences for new updates:

/**
 * Writes the material and sound differences to files in the server's root folder for updating purposes.
 */
public static void versionDifference() {
    Path serverFolder = Bukkit.getWorldContainer().toPath();
    Path materials = serverFolder.resolve("XMaterial.txt");
    Path sounds = serverFolder.resolve("XSound.txt");

    writeDifference(materials, Material.class, XMaterial.class);
    writeDifference(sounds, Sound.class, XSound.class);
}

/**
 * Writes the difference between two enums.
 * For other differences check:
 * <pre>
 *     https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/Material.java
 *     https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/Sound.java
 *     https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/potion/PotionEffectType.java
 *     https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/enchantments/Enchantment.java
 *     https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/Particle.java
 * </pre>
 *
 * @param path   the file path to write the difference to.
 * @param system the original enum.
 * @param custom the custom enum that is most likely a version behind the original enum.
 */
public static <S extends Enum<S>, E extends Enum<E>> void writeDifference(Path path, Class<S> system, Class<E> custom) {
    try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
        writer.write("Added:");
        writer.newLine();

        for (Enum<S> systemConst : system.getEnumConstants()) {
            boolean exists = true;
            for (Enum<E> customConst : custom.getEnumConstants()) {
                if (systemConst.name().equals(customConst.name())) {
                    exists = false;
                    break;
                }
            }
            if (exists) {
                writer.write(systemConst.name() + ',');
                writer.newLine();
            }
        }

        writer.newLine();
        writer.write("----------------- Removed -----------------");
        writer.newLine();

        for (Enum<E> customConst : custom.getEnumConstants()) {
            boolean exists = true;
            for (Enum<S> systemConst : system.getEnumConstants()) {
                if (systemConst.name().equals(customConst.name())) {
                    exists = false;
                    break;
                }
            }
            if (exists) {
                writer.write(customConst.name());
                writer.newLine();
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

1.8 Full Particle Support Based on ParticleDisplay

private static final boolean ONE_EIGHT;
private static final MethodHandle PACKET;
private static final Map<Effect, Object> ONE_EIGHT_ENUM_PARTICLE;

static {
    boolean oneEight;
    try {
        Class.forName("org.bukkit.Particle");
        oneEight = false;
    } catch (ClassNotFoundException ex) {
        oneEight = true;
    }
    ONE_EIGHT = oneEight;
}

static {
    if (ONE_EIGHT) {
        ONE_EIGHT_ENUM_PARTICLE = new EnumMap<>(Effect.class);
        Class<?> enumParticleClass = ReflectionUtils.getNMSClass("EnumParticle");
        for (Field particle : enumParticleClass.getDeclaredFields()) {
            if (particle.isEnumConstant()) {
                try {
                    Effect effect = Enums.getIfPresent(Effect.class, particle.getName()).orNull();
                    if (effect != null) ONE_EIGHT_ENUM_PARTICLE.put(effect, particle.get(enumParticleClass));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

        MethodHandle packet;
        try {
            // https://wiki.vg/Protocol#Particle_2
            packet = MethodHandles.lookup().findConstructor(ReflectionUtils.getNMSClass("PacketPlayOutWorldParticles"),
                    MethodType.methodType(enumParticleClass,
                            // Long Distance: If true, particle distance increases from 256 to 65536
                            boolean.class,
                            // x, y, z
                            float.class, float.class, float.class,
                            // Offset x, y, z
                            float.class, float.class, float.class,
                            // Particle Data
                            float.class,
                            // Amount  // Data https://wiki.vg/Protocol#Particle
                            int.class, int[].class));
        } catch (NoSuchMethodException | IllegalAccessException e) {
            e.printStackTrace();
            packet = null;
        }

        PACKET = packet;
    } else {
        ONE_EIGHT_ENUM_PARTICLE = null;
        PACKET = null;
    }
}

private void sendOneEightParticle(@Nonnull Location loc) {
    CompletableFuture.runAsync(() -> {
        Object packet;
        try {
            packet = PACKET.invoke(ONE_EIGHT_ENUM_PARTICLE.get(effect), false, (float) loc.getX(), (float) loc.getY(), (float) loc.getZ(),
                    (float) offsetx, (float) offsety, (float) offsetz, 0f, this.count, new int[this.data.hashCode()]);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return;
        }

        for (Player player : loc.getWorld().getPlayers()) {
            // BlockPosition blockposition = player.getChunkCoordinates();
            // if (blockposition.a(new Vec3D(d0, d1, d2), flag ? 512.0D : 32.0D))
            // Flag is always false

            // blockposition.a(new Vec3D(d0, d1, d2), 32.0D)
            // where d0, d1, d2 is x, y, z
            // a translates to this.distanceSquared(var0.getX(), var0.getY(), var0.getZ(), true) < var1 * var1
            // with var1 as "flag ? 512.0D : 32.0D"
            Location first = player.getLocation();
            double distanceSquared =
                    NumberConversions.square(first.getX() - loc.getX()) +
                            NumberConversions.square(first.getY() - loc.getY()) +
                            NumberConversions.square(first.getZ() - loc.getZ());
            if (distanceSquared < 32 * 32) ReflectionUtils.sendPacket(player, packet);
        }
    }).exceptionally(ex -> {
        ex.printStackTrace();
        return null;
    });
}
Clone this wiki locally