|
| 1 | +package me.croabeast.prismatic; |
| 2 | + |
| 3 | +import com.google.common.collect.Lists; |
| 4 | +import com.viaversion.viaversion.api.Via; |
| 5 | +import org.bukkit.Bukkit; |
| 6 | +import org.bukkit.entity.Player; |
| 7 | + |
| 8 | +import java.util.ArrayList; |
| 9 | +import java.util.Arrays; |
| 10 | +import java.util.List; |
| 11 | +import java.util.regex.Matcher; |
| 12 | +import java.util.regex.Pattern; |
| 13 | + |
| 14 | +/** |
| 15 | + * Provides a mapping between Minecraft client protocol versions and a simplified major version. |
| 16 | + * <p> |
| 17 | + * The {@code ClientVersion} class is used to determine the major client version from a player's protocol |
| 18 | + * version, which is helpful when implementing version-specific features or handling legacy clients. |
| 19 | + * It maintains an internal list of {@code ClientVersion} instances, each corresponding to a range of protocol |
| 20 | + * numbers. If no matching version is found, it returns an unknown version. |
| 21 | + * </p> |
| 22 | + * <p> |
| 23 | + * This class also offers utility methods to check if a client is considered legacy. |
| 24 | + * </p> |
| 25 | + * <p> |
| 26 | + * Example usage: |
| 27 | + * <pre><code> |
| 28 | + * int majorVersion = ClientVersion.getClientVersion(player); |
| 29 | + * boolean isLegacy = ClientVersion.isLegacy(player); |
| 30 | + * System.out.println("Player client major version: " + majorVersion); |
| 31 | + * </code></pre> |
| 32 | + * </p> |
| 33 | + * |
| 34 | + * @see Via |
| 35 | + */ |
| 36 | +public final class ClientVersion { |
| 37 | + |
| 38 | + /** |
| 39 | + * A list of all registered client version mappings. |
| 40 | + */ |
| 41 | + private static final List<ClientVersion> PROTOCOL_LIST = new ArrayList<>(); |
| 42 | + |
| 43 | + /** |
| 44 | + * Represents an unknown client version. |
| 45 | + */ |
| 46 | + private static final ClientVersion UNKNOWN = new ClientVersion(0, 0, 0); |
| 47 | + |
| 48 | + static final double SERVER_VERSION; |
| 49 | + |
| 50 | + static { |
| 51 | + double main = 0.0; |
| 52 | + |
| 53 | + Pattern p = Pattern.compile("1\\.(\\d+(\\.\\d+)?)"); |
| 54 | + Matcher m = p.matcher(Bukkit.getVersion()); |
| 55 | + |
| 56 | + if (m.find()) |
| 57 | + try { |
| 58 | + main = Double.parseDouble(m.group(1)); |
| 59 | + } catch (Exception ignored) { |
| 60 | + } |
| 61 | + |
| 62 | + SERVER_VERSION = main; |
| 63 | + |
| 64 | + new ClientVersion(7, 0, 5); |
| 65 | + new ClientVersion(8, 6, 47); |
| 66 | + new ClientVersion(9, 48, 110); |
| 67 | + new ClientVersion(10, 201, 210, fromInts(206, 209)); |
| 68 | + new ClientVersion(11, 301, 316); |
| 69 | + new ClientVersion(12, 317, 340); |
| 70 | + new ClientVersion(13, 341, 404); |
| 71 | + new ClientVersion(14, 441, 500, fromInts(499)); |
| 72 | + new ClientVersion(15, 550, 578); |
| 73 | + new ClientVersion(16, 701, 754); |
| 74 | + new ClientVersion(17, 755, 756); |
| 75 | + new ClientVersion(18, 757, 758); |
| 76 | + new ClientVersion(19, 759, 762); |
| 77 | + new ClientVersion(20, 763, 766); |
| 78 | + new ClientVersion(21, 767, 770); |
| 79 | + } |
| 80 | + |
| 81 | + /** |
| 82 | + * The major version number for this client version. |
| 83 | + */ |
| 84 | + private final int version; |
| 85 | + |
| 86 | + /** |
| 87 | + * A list of protocol numbers associated with this major client version. |
| 88 | + */ |
| 89 | + private final List<Integer> protocols; |
| 90 | + |
| 91 | + /** |
| 92 | + * Creates a list of integers representing a range from the given start to end (inclusive). |
| 93 | + * |
| 94 | + * @param numbers an array of two integers where the first is the start and the second is the end of the range |
| 95 | + * @return a list of protocol numbers in the specified range |
| 96 | + */ |
| 97 | + private static List<Integer> fromInts(Integer... numbers) { |
| 98 | + if (numbers.length != 2) |
| 99 | + return Lists.newArrayList(numbers); |
| 100 | + int z = numbers[1], y = numbers[0]; |
| 101 | + Integer[] array = new Integer[(z - y) + 1]; |
| 102 | + int index = 0; |
| 103 | + for (int i = y; i <= z; i++) { |
| 104 | + array[index] = i; |
| 105 | + index++; |
| 106 | + } |
| 107 | + return new ArrayList<>(Arrays.asList(array)); |
| 108 | + } |
| 109 | + |
| 110 | + /** |
| 111 | + * Constructs a new {@code ClientVersion} with the specified major version and protocol range, |
| 112 | + * optionally excluding certain protocol numbers. |
| 113 | + * |
| 114 | + * @param version the major version number |
| 115 | + * @param start the starting protocol number (inclusive) |
| 116 | + * @param end the ending protocol number (inclusive) |
| 117 | + * @param ignore a list of protocol numbers to ignore; may be {@code null} or empty |
| 118 | + */ |
| 119 | + private ClientVersion(int version, int start, int end, List<Integer> ignore) { |
| 120 | + this.version = version; |
| 121 | + List<Integer> range = fromInts(start, end); |
| 122 | + if (ignore == null || ignore.isEmpty()) { |
| 123 | + protocols = range; |
| 124 | + PROTOCOL_LIST.add(this); |
| 125 | + return; |
| 126 | + } |
| 127 | + range.removeIf(ignore::contains); |
| 128 | + protocols = range; |
| 129 | + PROTOCOL_LIST.add(this); |
| 130 | + } |
| 131 | + |
| 132 | + /** |
| 133 | + * Constructs a new {@code ClientVersion} with the specified major version and protocol range. |
| 134 | + * |
| 135 | + * @param version the major version number |
| 136 | + * @param start the starting protocol number (inclusive) |
| 137 | + * @param end the ending protocol number (inclusive) |
| 138 | + */ |
| 139 | + private ClientVersion(int version, int start, int end) { |
| 140 | + this(version, start, end, null); |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * Returns a string representation of the client version. |
| 145 | + * <p> |
| 146 | + * For unknown versions, it returns "UNKNOWN_CLIENT:0". Otherwise, it returns "CLIENT:" followed by the major version number. |
| 147 | + * </p> |
| 148 | + * |
| 149 | + * @return a string representing the client version |
| 150 | + */ |
| 151 | + @Override |
| 152 | + public String toString() { |
| 153 | + return version == 0 ? "UNKNOWN_CLIENT:0" : ("CLIENT:" + version); |
| 154 | + } |
| 155 | + |
| 156 | + /** |
| 157 | + * Returns an array containing all registered {@code ClientVersion} instances. |
| 158 | + * |
| 159 | + * @return an array of {@code ClientVersion} objects |
| 160 | + */ |
| 161 | + public static ClientVersion[] values() { |
| 162 | + return PROTOCOL_LIST.toArray(new ClientVersion[0]); |
| 163 | + } |
| 164 | + |
| 165 | + /** |
| 166 | + * Determines the major client version for the given player based on their protocol version. |
| 167 | + * <p> |
| 168 | + * If ViaVersion is not enabled or the player is {@code null}, the server's version is returned. |
| 169 | + * Otherwise, the player's protocol version is retrieved, and the corresponding major version is determined. |
| 170 | + * </p> |
| 171 | + * |
| 172 | + * @param player the player whose client version is to be determined; may be {@code null} |
| 173 | + * @return the major client version number, or {@code 0} for unknown versions |
| 174 | + */ |
| 175 | + public static int getClientVersion(Player player) { |
| 176 | + if (player == null) |
| 177 | + return (int) SERVER_VERSION; |
| 178 | + |
| 179 | + if (!Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) |
| 180 | + return (int) SERVER_VERSION; |
| 181 | + |
| 182 | + int i = Via.getAPI().getPlayerVersion(player.getUniqueId()); |
| 183 | + for (ClientVersion p : values()) { |
| 184 | + if (p == UNKNOWN) continue; |
| 185 | + if (p.protocols.contains(i)) return p.version; |
| 186 | + } |
| 187 | + |
| 188 | + return UNKNOWN.version; |
| 189 | + } |
| 190 | + |
| 191 | + /** |
| 192 | + * Checks whether the specified player's client is considered legacy. |
| 193 | + * <p> |
| 194 | + * A legacy client is defined as one with a major version of 15 or lower. |
| 195 | + * </p> |
| 196 | + * |
| 197 | + * @param player the player whose client version is to be checked |
| 198 | + * @return {@code true} if the player's client is legacy; {@code false} otherwise |
| 199 | + */ |
| 200 | + public static boolean isLegacy(Player player) { |
| 201 | + return getClientVersion(player) <= 15; |
| 202 | + } |
| 203 | +} |
0 commit comments