package net.minecraft.client; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.mojang.blaze3d.platform.InputConstants; import com.mojang.blaze3d.platform.InputConstants.Key; import com.mojang.blaze3d.platform.InputConstants.Type; import java.util.Map; import java.util.Set; import java.util.function.Supplier; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.Util; import net.minecraft.client.resources.language.I18n; import net.minecraft.network.chat.Component; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class KeyMapping implements Comparable { private static final Map ALL = Maps.newHashMap(); private static final Map MAP = Maps.newHashMap(); private static final Set CATEGORIES = Sets.newHashSet(); public static final String CATEGORY_MOVEMENT = "key.categories.movement"; public static final String CATEGORY_MISC = "key.categories.misc"; public static final String CATEGORY_MULTIPLAYER = "key.categories.multiplayer"; public static final String CATEGORY_GAMEPLAY = "key.categories.gameplay"; public static final String CATEGORY_INVENTORY = "key.categories.inventory"; public static final String CATEGORY_INTERFACE = "key.categories.ui"; public static final String CATEGORY_CREATIVE = "key.categories.creative"; private static final Map CATEGORY_SORT_ORDER = Util.make(Maps.newHashMap(), hashMap -> { hashMap.put("key.categories.movement", 1); hashMap.put("key.categories.gameplay", 2); hashMap.put("key.categories.inventory", 3); hashMap.put("key.categories.creative", 4); hashMap.put("key.categories.multiplayer", 5); hashMap.put("key.categories.ui", 6); hashMap.put("key.categories.misc", 7); }); private final String name; private final Key defaultKey; private final String category; private Key key; private boolean isDown; private int clickCount; public static void click(Key key) { KeyMapping keyMapping = (KeyMapping)MAP.get(key); if (keyMapping != null) { keyMapping.clickCount++; } } public static void set(Key key, boolean held) { KeyMapping keyMapping = (KeyMapping)MAP.get(key); if (keyMapping != null) { keyMapping.setDown(held); } } /** * Completely recalculates whether any keybinds are held, from scratch. */ public static void setAll() { for (KeyMapping keyMapping : ALL.values()) { if (keyMapping.key.getType() == Type.KEYSYM && keyMapping.key.getValue() != InputConstants.UNKNOWN.getValue()) { keyMapping.setDown(InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), keyMapping.key.getValue())); } } } public static void releaseAll() { for (KeyMapping keyMapping : ALL.values()) { keyMapping.release(); } } public static void resetToggleKeys() { for (KeyMapping keyMapping : ALL.values()) { if (keyMapping instanceof ToggleKeyMapping toggleKeyMapping) { toggleKeyMapping.reset(); } } } public static void resetMapping() { MAP.clear(); for (KeyMapping keyMapping : ALL.values()) { MAP.put(keyMapping.key, keyMapping); } } public KeyMapping(String name, int keyCode, String category) { this(name, Type.KEYSYM, keyCode, category); } public KeyMapping(String name, Type type, int keyCode, String category) { this.name = name; this.key = type.getOrCreate(keyCode); this.defaultKey = this.key; this.category = category; ALL.put(name, this); MAP.put(this.key, this); CATEGORIES.add(category); } /** * Returns {@code true} if the key is pressed (used for continuous querying). Should be used in tickers. */ public boolean isDown() { return this.isDown; } public String getCategory() { return this.category; } /** * Returns {@code true} on the initial key press. For continuous querying use {@link isKeyDown()}. Should be used in key events. */ public boolean consumeClick() { if (this.clickCount == 0) { return false; } else { this.clickCount--; return true; } } private void release() { this.clickCount = 0; this.setDown(false); } public String getName() { return this.name; } public Key getDefaultKey() { return this.defaultKey; } /** * Binds a new KeyCode to this */ public void setKey(Key key) { this.key = key; } public int compareTo(KeyMapping keyMapping) { return this.category.equals(keyMapping.category) ? I18n.get(this.name).compareTo(I18n.get(keyMapping.name)) : ((Integer)CATEGORY_SORT_ORDER.get(this.category)).compareTo((Integer)CATEGORY_SORT_ORDER.get(keyMapping.category)); } /** * Returns a supplier which gets a keybind's current binding (eg, key.forward returns W by default), or the keybind's name if no such keybind exists (eg, key.invalid returns key.invalid) */ public static Supplier createNameSupplier(String key) { KeyMapping keyMapping = (KeyMapping)ALL.get(key); return keyMapping == null ? () -> Component.translatable(key) : keyMapping::getTranslatedKeyMessage; } /** * Returns {@code true} if the supplied {@code KeyMapping} conflicts with this */ public boolean same(KeyMapping binding) { return this.key.equals(binding.key); } public boolean isUnbound() { return this.key.equals(InputConstants.UNKNOWN); } public boolean matches(int keysym, int scancode) { return keysym == InputConstants.UNKNOWN.getValue() ? this.key.getType() == Type.SCANCODE && this.key.getValue() == scancode : this.key.getType() == Type.KEYSYM && this.key.getValue() == keysym; } /** * Returns {@code true} if the {@code KeyMapping} is set to a mouse key and the key matches. */ public boolean matchesMouse(int key) { return this.key.getType() == Type.MOUSE && this.key.getValue() == key; } public Component getTranslatedKeyMessage() { return this.key.getDisplayName(); } /** * Returns {@code true} if the {@code KeyMapping} is using the default key and key modifier */ public boolean isDefault() { return this.key.equals(this.defaultKey); } public String saveString() { return this.key.getName(); } public void setDown(boolean value) { this.isDown = value; } @Nullable public static KeyMapping get(String name) { return (KeyMapping)ALL.get(name); } }