Free NeoForge 1.21.1 modpack on Cobblemon 1.8.4 built with packwiz, plus the Kotlin companion mod (cobblesurvival) and its built-in data pack. - Phase 0: packwiz pack with Modrinth/CurseForge cross-links, build and export tooling, Prism instance sync, test server with smoke checks. - Phase 1: Poke Ball shaped island (WorldGen Editor preset, seed isle-26), Adrian's biome list, no natural ore, structures without ore or vanilla mobs, no Nether portals, world template builder and world report. - Phase 2: permadeath (CobbleHardcoreMon plus the companion mod's gap fixes), Revival Charm, expensive Healing Machine, Lootr refreshing loot, in-game self-tests on Minecraft's GameTest framework. - Phase 3 so far: 14 zones with their own spawn pools and level bands (tools/zone_layout.py, tools/zone_spawns.py, "zone" spawn condition), Cold Sweat heat and cold as gates with boss-drop insulation recipes, spawn survey and hazard self-tests in tools/quick_check.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
3.0 KiB
Python
73 lines
3.0 KiB
Python
#!/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"
|
|
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.
|
|
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()
|