Build: every tool pinned (JDK, packwiz, Gradle plugins, Python packages), exports normalised, tools/check_reproducible.py proves two clean builds are byte-identical. Server-side balance: a server can load config/cobblesurvival/datapack.zip in place of the jar's data, /reload applies it, tools/server_update.py packages it and refuses when a client change is needed. JEI drop pages are sent by the server. Code-held balance numbers moved to tuning/server.json. Performance: spark in the pack (server only), tools/perf_test.py with fake players whose chunks really are active, docs/performance.md. Adrian's feedback: default keys R, U, J, §; drop pages link to Cobblemon Info and show real chances; bosses only hurt in battles; battle XP halved; channels between the islands low level; temperature ×3 with insulation doubled; hazards start at once; no item dots on the map; a wild attack starts a battle; the starter only faints; quest book on join with a basics page and an HMs page; regional forms obtainable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
3.2 KiB
Python
77 lines
3.2 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"
|
|
# 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()
|