Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration. Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
4.9 KiB
Python
143 lines
4.9 KiB
Python
#!/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()
|