#!/usr/bin/env python3 """ 将 level-prefabs/Level{N}.prefab 重命名为 Level{91600+N}.prefab,并同步 JSON 数据库。 与 Unity / 主站 config.js BEGINNING_REAL_LVID=91601 对齐:游戏 ID 从 91601 起(首关 Level91601)。 用法: python3 tools/renumber-level-prefabs.py --dry-run python3 tools/renumber-level-prefabs.py """ from __future__ import annotations import argparse import json import re from datetime import datetime, timezone from pathlib import Path from level_id import ( LEVEL_ID_BASE, internal_level_index as to_internal_id, prefab_resource_path, touch_database, ) def to_external_id(internal: int) -> int: return LEVEL_ID_BASE + internal PREFAB_DIR_NAME = "level-prefabs" LEVEL_RE = re.compile(r"^Level(\d+)\.prefab$", re.I) def rename_prefabs(prefab_dir: Path, dry_run: bool) -> dict[int, int]: """internal -> external mapping for renamed files.""" mapping: dict[int, int] = {} files = sorted( [p for p in prefab_dir.glob("Level*.prefab") if LEVEL_RE.match(p.name)], key=lambda p: int(LEVEL_RE.match(p.name).group(1)), # type: ignore reverse=True, ) for src in files: m = LEVEL_RE.match(src.name) if not m: continue internal = int(m.group(1)) if internal > LEVEL_ID_BASE: continue external = to_external_id(internal) dst = prefab_dir / f"Level{external}.prefab" meta_src = Path(str(src) + ".meta") meta_dst = Path(str(dst) + ".meta") mapping[internal] = external if dry_run: if internal <= 3 or internal in (60, 80, 4188): print(f" {src.name} -> {dst.name}") continue if dst.exists(): raise SystemExit(f"目标已存在,中止: {dst}") src.rename(dst) if meta_src.is_file(): meta_src.rename(meta_dst) return mapping def migrate_levels_database(db_path: Path, mapping: dict[int, int], dry_run: bool) -> None: if not db_path.is_file(): print(f"skip database (not found): {db_path}") return data = json.loads(db_path.read_text(encoding="utf-8")) old_levels = data.get("levels") or {} new_levels: dict[str, dict] = {} for key, cfg in old_levels.items(): try: old_id = int(key) except ValueError: old_id = int(cfg.get("levelID", key)) internal = to_internal_id(old_id) if old_id > LEVEL_ID_BASE else old_id external = mapping.get(internal, to_external_id(internal)) entry = dict(cfg) entry["levelID"] = external unity_prefab = entry.get("unityPrefab", "") if isinstance(unity_prefab, str) and unity_prefab: entry["unityPrefab"] = re.sub( r"Level\d+\.prefab$", f"Level{external}.prefab", unity_prefab.replace("\\", "/"), flags=re.I, ) entry["cocosPrefab"] = prefab_resource_path(external) new_levels[str(external)] = entry data["levels"] = new_levels if dry_run: print(f"database: {len(old_levels)} -> {len(new_levels)} keys:", sorted(new_levels.keys())) return db_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") touch_database(db_path) print(f"updated {db_path}") def rebuild_prefab_index(prefab_dir: Path, index_path: Path, dry_run: bool) -> None: index: dict[str, str] = {} for p in sorted( prefab_dir.glob("Level*.prefab"), key=lambda x: int(LEVEL_RE.match(x.name).group(1)), # type: ignore ): m = LEVEL_RE.match(p.name) if not m: continue lid = int(m.group(1)) index[str(lid)] = f"{PREFAB_DIR_NAME}/Level{lid}" if dry_run: print(f"index: {len(index)} entries") return index_path.write_text(json.dumps(index, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") print(f"updated {index_path} ({len(index)} entries)") def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--project", type=Path, default=Path(__file__).resolve().parents[1]) ap.add_argument("--dry-run", action="store_true") args = ap.parse_args() project = args.project prefab_dir = project / "assets" / "resources" / PREFAB_DIR_NAME db_path = project / "assets" / "level-data" / "levels-database.json" index_path = project / "tools" / "level-prefab-index.json" if not prefab_dir.is_dir(): raise SystemExit(f"prefab dir not found: {prefab_dir}") print(f"{'[dry-run] ' if args.dry_run else ''}Renaming prefabs in {prefab_dir} …") mapping = rename_prefabs(prefab_dir, args.dry_run) print(f" mapped {len(mapping)} prefabs (1 -> {to_external_id(1)})") migrate_levels_database(db_path, mapping, args.dry_run) rebuild_prefab_index(prefab_dir, index_path, args.dry_run) print("done.") if __name__ == "__main__": main()