ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java

1021 lines
28 KiB
Java

package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.ExactReflection;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import org.apache.commons.lang.Validate;
import org.bukkit.GameMode;
/**
* Represents a generic enum converter.
* @author Kristian
*/
@SuppressWarnings({"unchecked","rawtypes"})
public abstract class EnumWrappers {
public enum ClientCommand {
PERFORM_RESPAWN,
REQUEST_STATS,
OPEN_INVENTORY_ACHIEVEMENT
}
public enum ChatVisibility {
FULL,
SYSTEM,
HIDDEN
}
public enum Difficulty {
PEACEFUL,
EASY,
NORMAL,
HARD
}
public enum EntityUseAction {
INTERACT,
ATTACK,
INTERACT_AT
}
/**
* Represents a native game mode in Minecraft.
* <p>
* Not to be confused with {@link GameMode} in Bukkit.
*
* @author Kristian
*/
public enum NativeGameMode {
NOT_SET,
SURVIVAL,
CREATIVE,
ADVENTURE,
SPECTATOR,
/**
* @deprecated Replaced by NOT_SET
*/
@Deprecated
NONE;
/**
* Gets this NativeGameMode's Bukkit equivalent.
* <p>
* Note: There is not a Bukkit equivalent for NOT_SET or NONE
*
* @return The Bukkit equivalent, or null if one does not exist.
*/
public GameMode toBukkit() {
switch (this) {
case ADVENTURE:
return GameMode.ADVENTURE;
case CREATIVE:
return GameMode.CREATIVE;
case SPECTATOR:
return GameMode.SPECTATOR;
case SURVIVAL:
return GameMode.SURVIVAL;
default:
return null;
}
}
/**
* Obtains the given GameMode's NativeGameMode equivalent.
*
* @param mode Bukkit GameMode
* @return The NativeGameMode equivalent, or null if one does not exist.
*/
public static NativeGameMode fromBukkit(GameMode mode) {
switch (mode) {
case ADVENTURE:
return ADVENTURE;
case CREATIVE:
return CREATIVE;
case SPECTATOR:
return SPECTATOR;
case SURVIVAL:
return SURVIVAL;
default:
return null;
}
}
}
public enum ResourcePackStatus {
SUCCESSFULLY_LOADED,
DECLINED,
FAILED_DOWNLOAD,
ACCEPTED
}
public enum PlayerInfoAction {
ADD_PLAYER,
INITIALIZE_CHAT,
UPDATE_GAME_MODE,
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
/**
* @deprecated Removed in 1.19.3
*/
@Deprecated
REMOVE_PLAYER
}
public enum TitleAction {
TITLE,
SUBTITLE,
ACTIONBAR,
TIMES,
CLEAR,
RESET
}
public enum WorldBorderAction {
SET_SIZE,
LERP_SIZE,
SET_CENTER,
INITIALIZE,
SET_WARNING_TIME,
SET_WARNING_BLOCKS
}
public enum CombatEventType {
ENTER_COMBAT,
END_COMBAT,
ENTITY_DIED
}
public enum PlayerDigType implements AliasedEnum{
START_DESTROY_BLOCK,
ABORT_DESTROY_BLOCK,
STOP_DESTROY_BLOCK,
DROP_ALL_ITEMS,
DROP_ITEM,
RELEASE_USE_ITEM,
SWAP_HELD_ITEMS("SWAP_ITEM_WITH_OFFHAND");
String[] aliases;
PlayerDigType(String... aliases) {
this.aliases = aliases;
}
@Override
public String[] getAliases() {
return aliases;
}
}
public enum PlayerAction implements AliasedEnum {
START_SNEAKING("PRESS_SHIFT_KEY"),
STOP_SNEAKING("RELEASE_SHIFT_KEY"),
STOP_SLEEPING,
START_SPRINTING,
STOP_SPRINTING,
START_RIDING_JUMP,
STOP_RIDING_JUMP,
OPEN_INVENTORY,
START_FALL_FLYING;
String[] aliases;
PlayerAction(String... aliases) {
this.aliases = aliases;
}
@Override
public String[] getAliases() {
return aliases;
}
}
public enum ScoreboardAction {
CHANGE,
REMOVE
}
public enum Particle {
EXPLOSION_NORMAL("explode", 0, true),
EXPLOSION_LARGE("largeexplode", 1, true),
EXPLOSION_HUGE("hugeexplosion", 2, true),
FIREWORKS_SPARK("fireworksSpark", 3, false),
WATER_BUBBLE("bubble", 4, false),
WATER_SPLASH("splash", 5, false),
WATER_WAKE("wake", 6, false),
SUSPENDED("suspended", 7, false),
SUSPENDED_DEPTH("depthsuspend", 8, false),
CRIT("crit", 9, false),
CRIT_MAGIC("magicCrit", 10, false),
SMOKE_NORMAL("smoke", 11, false),
SMOKE_LARGE("largesmoke", 12, false),
SPELL("spell", 13, false),
SPELL_INSTANT("instantSpell", 14, false),
SPELL_MOB("mobSpell", 15, false),
SPELL_MOB_AMBIENT("mobSpellAmbient", 16, false),
SPELL_WITCH("witchMagic", 17, false),
DRIP_WATER("dripWater", 18, false),
DRIP_LAVA("dripLava", 19, false),
VILLAGER_ANGRY("angryVillager", 20, false),
VILLAGER_HAPPY("happyVillager", 21, false),
TOWN_AURA("townaura", 22, false),
NOTE("note", 23, false),
PORTAL("portal", 24, false),
ENCHANTMENT_TABLE("enchantmenttable", 25, false),
FLAME("flame", 26, false),
LAVA("lava", 27, false),
FOOTSTEP("footstep", 28, false),
CLOUD("cloud", 29, false),
REDSTONE("reddust", 30, false),
SNOWBALL("snowballpoof", 31, false),
SNOW_SHOVEL("snowshovel", 32, false),
SLIME("slime", 33, false),
HEART("heart", 34, false),
BARRIER("barrier", 35, false),
ITEM_CRACK("iconcrack", 36, false, 2),
BLOCK_CRACK("blockcrack", 37, false, 1),
BLOCK_DUST("blockdust", 38, false, 1),
WATER_DROP("droplet", 39, false),
ITEM_TAKE("take", 40, false),
MOB_APPEARANCE("mobappearance", 41, true),
DRAGON_BREATH("dragonbreath", 42, false),
END_ROD("endRod", 43, false),
DAMAGE_INDICATOR("damageIndicator", 44, true),
SWEEP_ATTACK("sweepAttack", 45, true),
FALLING_DUST("fallingdust", 46, false, 1),
TOTEM("totem", 47, false),
SPIT("spit", 48, true);
private static final Map<String, Particle> BY_NAME;
private static final Map<Integer, Particle> BY_ID;
static {
BY_ID = new HashMap<>();
BY_NAME = new HashMap<>();
for (Particle particle : values()) {
BY_NAME.put(particle.getName().toLowerCase(Locale.ENGLISH), particle);
BY_ID.put(particle.getId(), particle);
}
}
private final String name;
private final int id;
private final boolean longDistance;
private final int dataLength;
Particle(String name, int id, boolean longDistance) {
this(name, id, longDistance, 0);
}
Particle(String name, int id, boolean longDistance, int dataLength) {
this.name = name;
this.id = id;
this.longDistance = longDistance;
this.dataLength = dataLength;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public boolean isLongDistance() {
return longDistance;
}
public int getDataLength() {
return dataLength;
}
public static Particle getByName(String name) {
return BY_NAME.get(name.toLowerCase(Locale.ENGLISH));
}
public static Particle getById(int id) {
return BY_ID.get(id);
}
}
public enum SoundCategory {
MASTER("master"),
MUSIC("music"),
RECORDS("record"),
WEATHER("weather"),
BLOCKS("block"),
HOSTILE("hostile"),
NEUTRAL("neutral"),
PLAYERS("player"),
AMBIENT("ambient"),
VOICE("voice");
private static final Map<String, SoundCategory> LOOKUP;
static {
LOOKUP = new HashMap<>();
for (SoundCategory category : values()) {
LOOKUP.put(category.key, category);
}
}
private final String key;
SoundCategory(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public static SoundCategory getByKey(String key) {
return LOOKUP.get(key.toLowerCase(Locale.ENGLISH));
}
}
public enum ItemSlot {
MAINHAND,
OFFHAND,
FEET,
LEGS,
CHEST,
HEAD
}
public enum Hand {
MAIN_HAND,
OFF_HAND
}
public enum Direction {
DOWN,
UP,
NORTH,
SOUTH,
WEST,
EAST
}
public enum ChatType {
CHAT,
SYSTEM,
GAME_INFO;
public byte getId() {
return (byte) ordinal();
}
}
/**
* Wrapped EntityPose enum for use in Entity Metadata Packet.<br>
* Remember to use {@link #toNms()} when adding to a {@link WrappedDataWatcher}. <br>
* Serializer is obtained using Registry.get(EnumWrappers.getEntityPoseClass())
*
* @since 1.13
* @author Lewys Davies (Lew_)
*/
public enum EntityPose {
STANDING,
FALL_FLYING,
SLEEPING,
SWIMMING,
SPIN_ATTACK,
CROUCHING,
LONG_JUMPING,
DYING,
CROAKING,
USING_TONGUE,
SITTING,
ROARING,
SNIFFING,
EMERGING,
DIGGING;
private final static EquivalentConverter<EntityPose> POSE_CONVERTER = EnumWrappers.getEntityPoseConverter();
/**
* @param nms net.minecraft.server.EntityPose Object
* @return Wrapped {@link EntityPose}
*/
public static EntityPose fromNms(Object nms) {
if (POSE_CONVERTER == null) {
throw new IllegalStateException("EntityPose is only available in Minecraft version 1.13 +");
}
return POSE_CONVERTER.getSpecific(nms);
}
/** @return net.minecraft.server.EntityPose enum equivalent to this wrapper enum */
public Object toNms() {
if (POSE_CONVERTER == null) {
throw new IllegalStateException("EntityPose is only available in Minecraft version 1.13 +");
}
return POSE_CONVERTER.getGeneric(this);
}
}
public enum Dimension {
OVERWORLD(0),
THE_NETHER(-1),
THE_END(1);
private final int id;
Dimension(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
public static Dimension fromId(int id) {
switch (id) {
case 0: return Dimension.OVERWORLD;
case -1: return Dimension.THE_NETHER;
case 1: return Dimension.THE_END;
default: throw new IllegalArgumentException("Invalid dimension ID: " + id);
}
}
}
private static Class<?> PROTOCOL_CLASS = null;
private static Class<?> CLIENT_COMMAND_CLASS = null;
private static Class<?> CHAT_VISIBILITY_CLASS = null;
private static Class<?> DIFFICULTY_CLASS = null;
private static Class<?> ENTITY_USE_ACTION_CLASS = null;
private static Class<?> GAMEMODE_CLASS = null;
private static Class<?> RESOURCE_PACK_STATUS_CLASS = null;
private static Class<?> PLAYER_INFO_ACTION_CLASS = null;
private static Class<?> TITLE_ACTION_CLASS = null;
private static Class<?> WORLD_BORDER_ACTION_CLASS = null;
private static Class<?> COMBAT_EVENT_TYPE_CLASS = null;
private static Class<?> PLAYER_DIG_TYPE_CLASS = null;
private static Class<?> PLAYER_ACTION_CLASS = null;
private static Class<?> SCOREBOARD_ACTION_CLASS = null;
private static Class<?> PARTICLE_CLASS = null;
private static Class<?> SOUND_CATEGORY_CLASS = null;
private static Class<?> ITEM_SLOT_CLASS = null;
private static Class<?> HAND_CLASS = null;
private static Class<?> DIRECTION_CLASS = null;
private static Class<?> CHAT_TYPE_CLASS = null;
private static Class<?> ENTITY_POSE_CLASS = null;
private static boolean INITIALIZED = false;
private static Map<Class<?>, EquivalentConverter<?>> FROM_NATIVE = new HashMap<>();
private static Map<Class<?>, EquivalentConverter<?>> FROM_WRAPPER = new HashMap<>();
static Set<String> INVALID = new HashSet<>();
/**
* Initialize the wrappers, if we haven't already.
*/
private static void initialize() {
if (INITIALIZED)
return;
INITIALIZED = true;
PROTOCOL_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0);
CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0);
CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0);
try {
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.SERVER_DIFFICULTY.getPacketClass(), 0);
} catch (Exception ex) {
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 1);
}
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0);
RESOURCE_PACK_STATUS_CLASS = getEnum(PacketType.Play.Client.RESOURCE_PACK_STATUS.getPacketClass(), 0);
TITLE_ACTION_CLASS = getEnum(PacketType.Play.Server.TITLE.getPacketClass(), 0);
WORLD_BORDER_ACTION_CLASS = getEnum(PacketType.Play.Server.WORLD_BORDER.getPacketClass(), 0);
COMBAT_EVENT_TYPE_CLASS = getEnum(PacketType.Play.Server.COMBAT_EVENT.getPacketClass(), 0);
PLAYER_DIG_TYPE_CLASS = getEnum(PacketType.Play.Client.BLOCK_DIG.getPacketClass(), 1);
PLAYER_ACTION_CLASS = getEnum(PacketType.Play.Client.ENTITY_ACTION.getPacketClass(), 0);
SCOREBOARD_ACTION_CLASS = getEnum(PacketType.Play.Server.SCOREBOARD_SCORE.getPacketClass(), 0);
PARTICLE_CLASS = getEnum(PacketType.Play.Server.WORLD_PARTICLES.getPacketClass(), 0);
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
if (PLAYER_INFO_ACTION_CLASS == null) {
// todo: we can also use getField(0).getGenericType().getTypeParameters()[0]; but this should hold for now
PLAYER_INFO_ACTION_CLASS = PacketType.Play.Server.PLAYER_INFO.getPacketClass().getClasses()[1];
}
try {
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
} catch (Exception ex) {
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), 0);
}
try {
// TODO enum names are more stable than their packet associations
ITEM_SLOT_CLASS = MinecraftReflection.getMinecraftClass("world.entity.EnumItemSlot", "world.entity.EquipmentSlot", "EnumItemSlot");
} catch (Exception ex) {
ITEM_SLOT_CLASS = getEnum(PacketType.Play.Server.ENTITY_EQUIPMENT.getPacketClass(), 0);
}
// In 1.17 the hand and use action class is no longer a field in the packet
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand", "world.InteractionHand");
// class is named 'b' in the packet but class order differs in spigot and paper so we can only use the first method's return type (safest way)
ENTITY_USE_ACTION_CLASS = MinecraftReflection.getEnumEntityUseActionClass().getMethods()[0].getReturnType();
} else {
HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1);
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
}
// 1.19 removed the entity spawn packet and moved the direction into a seperated class
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
DIRECTION_CLASS = MinecraftReflection.getMinecraftClass("core.EnumDirection", "core.Direction");
} else {
DIRECTION_CLASS = getEnum(PacketType.Play.Server.SPAWN_ENTITY_PAINTING.getPacketClass(), 0);
}
CHAT_TYPE_CLASS = getEnum(PacketType.Play.Server.CHAT.getPacketClass(), 0);
ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("world.entity.EntityPose", "world.entity.Pose", "EntityPose");
associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter());
associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter());
associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter());
associate(DIFFICULTY_CLASS, Difficulty.class, getDifficultyConverter());
associate(GAMEMODE_CLASS, NativeGameMode.class, getGameModeConverter());
associate(RESOURCE_PACK_STATUS_CLASS, ResourcePackStatus.class, getResourcePackStatusConverter());
associate(PLAYER_INFO_ACTION_CLASS, PlayerInfoAction.class, getPlayerInfoActionConverter());
associate(TITLE_ACTION_CLASS, TitleAction.class, getTitleActionConverter());
associate(WORLD_BORDER_ACTION_CLASS, WorldBorderAction.class, getWorldBorderActionConverter());
associate(COMBAT_EVENT_TYPE_CLASS, CombatEventType.class, getCombatEventTypeConverter());
associate(PLAYER_DIG_TYPE_CLASS, PlayerDigType.class, getPlayerDiggingActionConverter());
associate(PLAYER_ACTION_CLASS, PlayerAction.class, getEntityActionConverter());
associate(SCOREBOARD_ACTION_CLASS, ScoreboardAction.class, getUpdateScoreActionConverter());
associate(PARTICLE_CLASS, Particle.class, getParticleConverter());
associate(SOUND_CATEGORY_CLASS, SoundCategory.class, getSoundCategoryConverter());
associate(ITEM_SLOT_CLASS, ItemSlot.class, getItemSlotConverter());
associate(DIRECTION_CLASS, Direction.class, getDirectionConverter());
associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter());
associate(HAND_CLASS, Hand.class, getHandConverter());
associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter());
if (ENTITY_POSE_CLASS != null) {
associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter());
}
}
private static void associate(Class<?> nativeClass, Class<?> wrapperClass, EquivalentConverter<?> converter) {
if (nativeClass != null) {
FROM_NATIVE.put(nativeClass, converter);
FROM_WRAPPER.put(wrapperClass, converter);
} else {
INVALID.add(wrapperClass.getSimpleName());
}
}
/**
* Retrieve the enum field with the given declaration index (in relation to the other enums).
* @param clazz - the declaration class.
* @param index - the enum index.
* @return The type of the enum field.
*/
private static Class<?> getEnum(Class<?> clazz, int index) {
if (clazz == null) {
// not supported in the current version
return null;
}
List<Field> enumFields = FuzzyReflection.fromClass(clazz, true).getFieldListByType(Enum.class);
if (enumFields.size() <= index) {
// also probably not supported
ProtocolLogger.debug("Enum field not found at index {0} of {1}", index, clazz);
return null;
}
return enumFields.get(index).getType();
}
public static Map<Class<?>, EquivalentConverter<?>> getFromNativeMap() {
return FROM_NATIVE;
}
public static Map<Class<?>, EquivalentConverter<?>> getFromWrapperMap() {
return FROM_WRAPPER;
}
// Get the native enum classes
public static Class<?> getProtocolClass() {
initialize();
return PROTOCOL_CLASS;
}
public static Class<?> getClientCommandClass() {
initialize();
return CLIENT_COMMAND_CLASS;
}
public static Class<?> getChatVisibilityClass() {
initialize();
return CHAT_VISIBILITY_CLASS;
}
public static Class<?> getDifficultyClass() {
initialize();
return DIFFICULTY_CLASS;
}
public static Class<?> getEntityUseActionClass() {
initialize();
return ENTITY_USE_ACTION_CLASS;
}
public static Class<?> getGameModeClass() {
initialize();
return GAMEMODE_CLASS;
}
public static Class<?> getResourcePackStatusClass() {
initialize();
return RESOURCE_PACK_STATUS_CLASS;
}
public static Class<?> getPlayerInfoActionClass() {
initialize();
return PLAYER_INFO_ACTION_CLASS;
}
public static Class<?> getTitleActionClass() {
initialize();
return TITLE_ACTION_CLASS;
}
public static Class<?> getWorldBorderActionClass() {
initialize();
return WORLD_BORDER_ACTION_CLASS;
}
public static Class<?> getCombatEventTypeClass() {
initialize();
return COMBAT_EVENT_TYPE_CLASS;
}
public static Class<?> getPlayerDigTypeClass() {
initialize();
return PLAYER_DIG_TYPE_CLASS;
}
public static Class<?> getPlayerActionClass() {
initialize();
return PLAYER_ACTION_CLASS;
}
public static Class<?> getScoreboardActionClass() {
initialize();
return SCOREBOARD_ACTION_CLASS;
}
public static Class<?> getParticleClass() {
initialize();
return PARTICLE_CLASS;
}
public static Class<?> getSoundCategoryClass() {
initialize();
return SOUND_CATEGORY_CLASS;
}
public static Class<?> getItemSlotClass() {
initialize();
return ITEM_SLOT_CLASS;
}
public static Class<?> getHandClass() {
initialize();
return HAND_CLASS;
}
public static Class<?> getDirectionClass() {
initialize();
return DIRECTION_CLASS;
}
public static Class<?> getChatTypeClass() {
initialize();
return CHAT_TYPE_CLASS;
}
public static Class<?> getEntityPoseClass() {
initialize();
return ENTITY_POSE_CLASS;
}
// Get the converters
public static EquivalentConverter<Protocol> getProtocolConverter() {
return new EnumConverter<>(getProtocolClass(), Protocol.class);
}
public static EquivalentConverter<ClientCommand> getClientCommandConverter() {
return new EnumConverter<>(getClientCommandClass(), ClientCommand.class);
}
public static EquivalentConverter<ChatVisibility> getChatVisibilityConverter() {
return new EnumConverter<>(getChatVisibilityClass(), ChatVisibility.class);
}
public static EquivalentConverter<Difficulty> getDifficultyConverter() {
return new EnumConverter<>(getDifficultyClass(), Difficulty.class);
}
public static EquivalentConverter<EntityUseAction> getEntityUseActionConverter() {
return new EnumConverter<>(getEntityUseActionClass(), EntityUseAction.class);
}
public static EquivalentConverter<NativeGameMode> getGameModeConverter() {
return new EnumConverter<>(getGameModeClass(), NativeGameMode.class);
}
public static EquivalentConverter<ResourcePackStatus> getResourcePackStatusConverter() {
return new EnumConverter<>(getResourcePackStatusClass(), ResourcePackStatus.class);
}
public static EquivalentConverter<PlayerInfoAction> getPlayerInfoActionConverter() {
return new EnumConverter<>(getPlayerInfoActionClass(), PlayerInfoAction.class);
}
public static EquivalentConverter<TitleAction> getTitleActionConverter() {
return new EnumConverter<>(getTitleActionClass(), TitleAction.class);
}
public static EquivalentConverter<WorldBorderAction> getWorldBorderActionConverter() {
return new EnumConverter<>(getWorldBorderActionClass(), WorldBorderAction.class);
}
public static EquivalentConverter<CombatEventType> getCombatEventTypeConverter() {
return new EnumConverter<>(getCombatEventTypeClass(), CombatEventType.class);
}
public static EquivalentConverter<PlayerDigType> getPlayerDiggingActionConverter() {
return new AliasedEnumConverter<>(getPlayerDigTypeClass(), PlayerDigType.class);
}
public static EquivalentConverter<PlayerAction> getEntityActionConverter() {
return new AliasedEnumConverter<>(getPlayerActionClass(), PlayerAction.class);
}
public static EquivalentConverter<ScoreboardAction> getUpdateScoreActionConverter() {
return new EnumConverter<>(getScoreboardActionClass(), ScoreboardAction.class);
}
public static EquivalentConverter<Particle> getParticleConverter() {
return new EnumConverter<>(getParticleClass(), Particle.class);
}
public static EquivalentConverter<SoundCategory> getSoundCategoryConverter() {
return new EnumConverter<>(getSoundCategoryClass(), SoundCategory.class);
}
public static EquivalentConverter<ItemSlot> getItemSlotConverter() {
return new EnumConverter<>(getItemSlotClass(), ItemSlot.class);
}
public static EquivalentConverter<Hand> getHandConverter() {
return new EnumConverter<>(getHandClass(), Hand.class);
}
public static EquivalentConverter<Direction> getDirectionConverter() {
return new EnumConverter<>(getDirectionClass(), Direction.class);
}
public static EquivalentConverter<ChatType> getChatTypeConverter() {
return new EnumConverter<>(getChatTypeClass(), ChatType.class);
}
/**
* @since 1.13+
* @return {@link EnumConverter} or null (if bellow 1.13 / nms EnumPose class cannot be found)
*/
public static EquivalentConverter<EntityPose> getEntityPoseConverter() {
if (getEntityPoseClass() == null) return null;
return new EnumConverter<>(getEntityPoseClass(), EntityPose.class);
}
/**
* Retrieve a generic enum converter for use with StructureModifiers.
* @param genericClass - Generic nms enum class
* @param specificType - Specific enum class
* @return A generic enum converter
*/
public static <T extends Enum<T>> EquivalentConverter<T> getGenericConverter(Class<?> genericClass, Class<T> specificType) {
return new EnumConverter<>(genericClass, specificType);
}
/**
* Creates an enum set with no elements based off the given class. The given must be an enum.
*
* @param clazz the element type of the enum set
* @return a new enum set with the given class as its element type
* @throws ClassCastException if the given class is not an enum
*/
public static <E extends Enum<E>> EnumSet<E> createEmptyEnumSet(Class<?> clazz) {
return EnumSet.noneOf((Class<E>) clazz);
}
/**
* The common Enum converter
*/
public static class EnumConverter<T extends Enum<T>> implements EquivalentConverter<T> {
private final Class<?> genericType;
private final Class<T> specificType;
public EnumConverter(Class<?> genericType, Class<T> specificType) {
Validate.notNull(specificType, "specificType cannot be null");
// would love to check if genericType is null, but it breaks other stuff
this.genericType = genericType;
this.specificType = specificType;
}
@Override
public T getSpecific(Object generic) {
return Enum.valueOf(specificType, ((Enum) generic).name());
}
@Override
public Object getGeneric(T specific) {
return Enum.valueOf((Class) genericType, specific.name());
}
@Override
public Class<T> getSpecificType() {
return specificType;
}
}
public interface AliasedEnum {
String[] getAliases();
}
/**
* Enums whose name has changed across NMS versions. Enums using this must also implement {@link AliasedEnum}
*/
public static class AliasedEnumConverter<T extends Enum<T> & AliasedEnum> implements EquivalentConverter<T> {
private Class<?> genericType;
private Class<T> specificType;
private Map<T, Object> genericMap = new ConcurrentHashMap<>();
private Map<Object, T> specificMap = new ConcurrentHashMap<>();
public AliasedEnumConverter(Class<?> genericType, Class<T> specificType) {
this.genericType = genericType;
this.specificType = specificType;
}
@Override
public T getSpecific(Object generic) {
return specificMap.computeIfAbsent(generic, x -> {
String name = ((Enum) generic).name();
try {
return Enum.valueOf(specificType, name);
} catch (Exception ex) {
for (T elem : specificType.getEnumConstants()) {
for (String alias : elem.getAliases()) {
if (alias.equals(name)) {
return elem;
}
}
}
}
throw new IllegalArgumentException("Unknown enum constant " + name);
});
}
@Override
public Object getGeneric(T specific) {
return genericMap.computeIfAbsent(specific, x -> {
String name = specific.name();
try {
return Enum.valueOf((Class) genericType, specific.name());
} catch (Exception ex) {
for (Object rawElem : genericType.getEnumConstants()) {
Enum elem = (Enum) rawElem;
for (String alias : specific.getAliases()) {
if (alias.equals(elem.name())) {
return elem;
}
}
}
}
throw new IllegalArgumentException("Unknown enum constant " + name);
});
}
@Override
public Class<T> getSpecificType() {
return specificType;
}
}
/**
* Used for classes where it's an enum in everything but name
* @param <T> Generic type
*/
public static class FauxEnumConverter<T extends Enum<T>> implements EquivalentConverter<T> {
private final Class<T> specificClass;
private final Class<?> genericClass;
private final Map<Object, T> lookup;
public FauxEnumConverter(Class<T> specific, Class<?> generic) {
Validate.notNull(specific,"specific class cannot be null");
Validate.notNull(generic,"generic class cannot be null");
this.specificClass = specific;
this.genericClass = generic;
this.lookup = new HashMap<>();
}
@Override
public Object getGeneric(T specific) {
Validate.notNull(specific, "specific object cannot be null");
Field field = ExactReflection.fromClass(this.genericClass, false).findField(specific.name());
return Accessors.getFieldAccessor(field).get(null);
}
@Override
public T getSpecific(Object generic) {
Validate.notNull(generic, "generic object cannot be null");
return lookup.computeIfAbsent(generic, x -> {
for (Field field : genericClass.getDeclaredFields()) {
try {
if (!field.isAccessible()) {
field.setAccessible(true);
}
if (field.get(null) == generic) {
return Enum.valueOf(specificClass, field.getName().toUpperCase());
}
} catch (ReflectiveOperationException ignored) { }
}
throw new IllegalArgumentException("Could not find ProtocolLib wrapper for " + generic);
});
}
@Override
public Class<T> getSpecificType() {
return specificClass;
}
}
public static class IndexedEnumConverter<T extends Enum<T>> implements EquivalentConverter<T> {
private Class<T> specificClass;
private Class<?> genericClass;
public IndexedEnumConverter(Class<T> specificClass, Class<?> genericClass) {
this.specificClass = specificClass;
this.genericClass = genericClass;
}
@Override
public Object getGeneric(T specific) {
int ordinal = specific.ordinal();
for (Object elem : genericClass.getEnumConstants()) {
if (((Enum<?>) elem).ordinal() == ordinal) {
return elem;
}
}
return null;
}
@Override
public T getSpecific(Object generic) {
int ordinal = ((Enum<?>) generic).ordinal();
for (T elem : specificClass.getEnumConstants()) {
if (elem.ordinal() == ordinal) {
return elem;
}
}
return null;
}
@Override
public Class<T> getSpecificType() {
return specificClass;
}
}
}