# ============================================================ # 数据包(moduleId: state-packs, 可重生 ✅) # 把「一套完整世界数据」封装为可重放的 JSON 包: # - 演示厂 = server/data/packs/demo.json(由脚本从种子生成) # - 行业种子 / 客户现场数据都可做成包,data.reset 即「重放数据包」 # 日期重基:包内记录 baseDate(生成日),加载时把所有 YYYY-MM-DD # 字面日期整体平移到「今天」,保证任意运行日行为一致(黄金测试稳定)。 # ============================================================ from __future__ import annotations import json import os import re from datetime import datetime, timedelta from typing import Any PACKS_DIR = os.path.join("server", "data", "packs") _DATE_RE = re.compile(r"^(\d{4}-\d{2}-\d{2})") def export_pack(world: dict[str, Any], path: str, *, name: str = "", description: str = "") -> None: """把世界数据导出为数据包(含日期基准,供加载时重基)。""" pack = { "packVersion": 1, "name": name or os.path.splitext(os.path.basename(path))[0], "description": description, "baseDate": datetime.now().strftime("%Y-%m-%d"), "world": world, } os.makedirs(os.path.dirname(path) or ".", exist_ok=True) with open(path, "w", encoding="utf-8") as f: json.dump(pack, f, ensure_ascii=False, indent=1) def _shift_dates(obj: Any, delta_days: int) -> Any: """递归平移所有 YYYY-MM-DD 开头的字符串日期。""" if delta_days == 0: return obj if isinstance(obj, dict): return {k: _shift_dates(v, delta_days) for k, v in obj.items()} if isinstance(obj, list): return [_shift_dates(v, delta_days) for v in obj] if isinstance(obj, str): m = _DATE_RE.match(obj) if m: try: d = datetime.strptime(m.group(1), "%Y-%m-%d") + timedelta(days=delta_days) return d.strftime("%Y-%m-%d") + obj[10:] except ValueError: return obj return obj def load_pack(path: str) -> dict[str, Any]: """读取数据包并把日期重基到今天,返回 world dict。""" with open(path, "r", encoding="utf-8") as f: pack = json.load(f) world = pack.get("world") or {} base = pack.get("baseDate") if base: try: delta = (datetime.now().date() - datetime.strptime(base, "%Y-%m-%d").date()).days world = _shift_dates(world, delta) except ValueError: pass return world def list_packs() -> list[dict[str, Any]]: """列出可用数据包(名称/描述/路径)。""" out: list[dict[str, Any]] = [] if not os.path.isdir(PACKS_DIR): return out for fn in sorted(os.listdir(PACKS_DIR)): if not fn.endswith(".json"): continue path = os.path.join(PACKS_DIR, fn) try: with open(path, "r", encoding="utf-8") as f: head = json.load(f) out.append({"file": fn, "name": head.get("name") or fn, "description": head.get("description") or "", "baseDate": head.get("baseDate") or "", "path": path}) except (json.JSONDecodeError, OSError): continue return out