initial commit
This commit is contained in:
Executable
+155
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
symlink_models.py — place this file and tags.json in the "Sven Co-op" root folder.
|
||||
|
||||
Creates a relative symlink for every model:
|
||||
svencoop_addon/models/player/<id>/<id>.mdl -> ../../../../svencoop/models/player/helmet/helmet.mdl
|
||||
|
||||
Sources (pick one, or combine):
|
||||
default — model IDs from tags.json, grouped by category
|
||||
--server PATH — scan any svencoop_addon/models/player/ directory for .mdl files
|
||||
--from-file FILE — read one model ID per line from a text file
|
||||
|
||||
Windows: requires Developer Mode or Administrator privileges to create symlinks.
|
||||
|
||||
Dump server model IDs to a file (run on the server or over a mount):
|
||||
find /home/svenserver/serverfiles/svencoop_addon/models/player \\
|
||||
-mindepth 2 -maxdepth 2 -name "*.mdl" -printf "%f\\n" | sed 's/\\.mdl$//' > server_models.txt
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HELMET_REL = Path("svencoop") / "models" / "player" / "helmet" / "helmet.mdl"
|
||||
MODELS_REL = Path("svencoop_addon") / "models" / "player"
|
||||
SYMLINK_TARGET = Path("..") / ".." / ".." / ".." / HELMET_REL
|
||||
|
||||
|
||||
def check_windows_symlink_privilege():
|
||||
if os.name != "nt":
|
||||
return True
|
||||
import ctypes
|
||||
try:
|
||||
return ctypes.windll.shell32.IsUserAnAdmin() != 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def symlink_one(model_id: str, models_root: Path, target_str: str, dry_run: bool) -> str:
|
||||
"""Create/update symlink for one model_id. Returns 'created', 'replaced', 'skipped', 'error'."""
|
||||
model_dir = models_root / model_id
|
||||
mdl_path = model_dir / f"{model_id}.mdl"
|
||||
|
||||
if not model_dir.is_dir() and not dry_run:
|
||||
model_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if mdl_path.is_symlink():
|
||||
if os.readlink(mdl_path) == target_str:
|
||||
return "skipped"
|
||||
if not dry_run:
|
||||
mdl_path.unlink()
|
||||
print(f" replace : {model_id}")
|
||||
return "replaced"
|
||||
elif mdl_path.exists():
|
||||
if not dry_run:
|
||||
mdl_path.unlink()
|
||||
print(f" replace : {model_id} (was real file)")
|
||||
return "replaced"
|
||||
else:
|
||||
print(f" create : {model_id}")
|
||||
|
||||
if not dry_run:
|
||||
try:
|
||||
os.symlink(target_str, mdl_path)
|
||||
except OSError as exc:
|
||||
print(f" ERROR : {model_id} — {exc}")
|
||||
return "error"
|
||||
return "created"
|
||||
|
||||
|
||||
def print_summary(counts: dict, dry_run: bool):
|
||||
total = counts["created"] + counts["replaced"]
|
||||
prefix = "[DRY RUN] " if dry_run else ""
|
||||
verb = "would be written" if dry_run else "written"
|
||||
print(
|
||||
f"\n{prefix}{total} symlinks {verb} "
|
||||
f"({counts['created']} new, {counts['replaced']} replaced), "
|
||||
f"{counts['skipped']} already correct"
|
||||
+ (f", {counts['error']} errors" if counts["error"] else "")
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Symlink Sven Co-op player models to helmet.mdl")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Show what would happen without making changes")
|
||||
parser.add_argument("--server", metavar="PATH", help="Scan a server's svencoop_addon/models/player/ directory")
|
||||
parser.add_argument("--from-file", metavar="FILE", help="Read model IDs (one per line) from a text file")
|
||||
args = parser.parse_args()
|
||||
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
helmet_abs = script_dir / HELMET_REL
|
||||
|
||||
if not helmet_abs.exists():
|
||||
sys.exit(f"helmet.mdl not found at {helmet_abs}\nAre you running from the Sven Co-op root?")
|
||||
|
||||
if os.name == "nt" and not check_windows_symlink_privilege():
|
||||
print(
|
||||
"WARNING: On Windows, symlink creation requires either:\n"
|
||||
" • Developer Mode enabled (Settings > For developers)\n"
|
||||
" • Running this script as Administrator\n"
|
||||
)
|
||||
|
||||
models_root = script_dir / MODELS_REL
|
||||
target_str = str(SYMLINK_TARGET)
|
||||
counts = {"created": 0, "replaced": 0, "skipped": 0, "error": 0}
|
||||
|
||||
# --- --server: scan a player/ directory for all .mdl files ---
|
||||
if args.server:
|
||||
server_root = Path(args.server)
|
||||
if not server_root.is_dir():
|
||||
sys.exit(f"Server path not found: {server_root}")
|
||||
|
||||
mdl_files = sorted(server_root.rglob("*.mdl"))
|
||||
model_ids = sorted({f.stem for f in mdl_files if f.parent != server_root})
|
||||
print(f"\n[server] — {len(model_ids)} models found in {server_root}\n")
|
||||
for model_id in model_ids:
|
||||
result = symlink_one(model_id, models_root, target_str, args.dry_run)
|
||||
counts[result] += 1
|
||||
print_summary(counts, args.dry_run)
|
||||
return
|
||||
|
||||
# --- --from-file: read model IDs from a text file ---
|
||||
if args.from_file:
|
||||
file_path = Path(args.from_file)
|
||||
if not file_path.exists():
|
||||
sys.exit(f"File not found: {file_path}")
|
||||
model_ids = [line.strip() for line in file_path.read_text().splitlines() if line.strip()]
|
||||
print(f"\n[from file] — {len(model_ids)} models in {file_path}\n")
|
||||
for model_id in model_ids:
|
||||
result = symlink_one(model_id, models_root, target_str, args.dry_run)
|
||||
counts[result] += 1
|
||||
print_summary(counts, args.dry_run)
|
||||
return
|
||||
|
||||
# --- default: tags.json ---
|
||||
tags_path = script_dir / "tags.json"
|
||||
if not tags_path.exists():
|
||||
sys.exit(f"tags.json not found next to script ({tags_path})")
|
||||
|
||||
with open(tags_path, encoding="utf-8") as f:
|
||||
tags: dict[str, list[str]] = json.load(f)
|
||||
|
||||
for category, model_ids in tags.items():
|
||||
print(f"\n[{category}] — {len(model_ids)} models")
|
||||
for model_id in model_ids:
|
||||
result = symlink_one(model_id, models_root, target_str, args.dry_run)
|
||||
counts[result] += 1
|
||||
|
||||
print_summary(counts, args.dry_run)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user