87 lines
3.3 KiB
Python
87 lines
3.3 KiB
Python
|
|
# ============================================================
|
|||
|
|
# 数据包(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
|