#!/usr/bin/env python3 """Report keyboard keys that more than one keybind uses, with the pack's defaults applied. NeoForge's Controls screen marks two keybinds as conflicting whenever they share a base key, even when one only works in GUIs and the other only in-game (its KeyMapping.same() falls back to comparing keys). The only exception is two binds in the same context with different modifiers (Ctrl+F next to F). So the pack's defaults give every keyboard bind its own base key. The list of keybinds comes from a client's options.txt (written on the first launch), so run this after adding a mod and launching the game once. Mouse buttons are left out: JEI's click actions in its item list share them with attack/use/pick block by design. Usage: tools/check_keybinds.py [options.txt] (default: the Prism dev instance) """ import sys from collections import defaultdict from pathlib import Path ROOT = Path(__file__).resolve().parent.parent DEFAULTS = ROOT / "pack/config/defaultoptions/keybindings.txt" # Share a key on purpose (Adrian, 2026-11-09): JEI's only works in a screen, Cobblemon's sends out a Pokémon in the world. BY_DESIGN = [{"key.jei.showRecipe", "key.cobblemon.throwpartypokemon"}] INSTANCE = (Path.home() / ".var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher/instances" / "Cobblemon Survival Pack (dev)/minecraft/options.txt") def parse(lines): binds = {} for line in lines: if not line.startswith("key_"): continue name, _, rest = line[4:].partition(":") key, _, modifiers = rest.partition(":") binds[name] = (key, modifiers.replace("NONE", "")) return binds def main(): options = Path(sys.argv[1]) if len(sys.argv) > 1 else INSTANCE if not options.exists(): sys.exit(f"{options} not found; launch the game once so it is written") binds = parse(options.read_text().splitlines()) defaults = parse(DEFAULTS.read_text().splitlines()) unknown = sorted(set(defaults) - set(binds)) binds.update(defaults) by_key = defaultdict(list) for name, (key, modifiers) in binds.items(): if key.startswith("key.keyboard.") and key != "key.keyboard.unknown": by_key[key].append((name, modifiers)) def clashes(entries): # One mod's binds share a context, so distinct modifiers keep them apart (e.g. JEI's # Page Down and Shift+Page Down). Anything else on the same key shows as a conflict. if {name for name, _ in entries} in BY_DESIGN: return False mods = {name.rsplit(".", 1)[0] for name, _ in entries} return len(entries) > 1 and not (len(mods) == 1 and len({m for _, m in entries}) == len(entries)) shared = {k: [f"{n}{' +' + m if m else ''}" for n, m in v] for k, v in sorted(by_key.items()) if clashes(v)} print(f"{len(binds)} keybinds, {len(defaults)} set by the pack, {len(by_key)} keyboard keys in use") for name in unknown: print(f" warning: the pack sets {name}, which this client doesn't have") if shared: print("keys used by more than one keybind:") for key, names in shared.items(): print(f" {key}: {', '.join(names)}") sys.exit(1) print("no keyboard key is used twice") if __name__ == "__main__": main()