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
|