mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-10-29 13:40:00 +08:00
add # 新增项目整体大纲
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
"""从只读旧作物库可复现导出独立目录,不访问玩家库或远程。"""
|
||||
|
||||
import argparse
|
||||
from contextlib import closing
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
|
||||
|
||||
def export(source: Path) -> bytes:
|
||||
source = source.resolve()
|
||||
with closing(sqlite3.connect(
|
||||
source.as_uri() + "?mode=ro", uri=True,
|
||||
)) as connection:
|
||||
connection.row_factory = sqlite3.Row
|
||||
rows = [
|
||||
dict(row) for row in connection.execute(
|
||||
"SELECT * FROM plant ORDER BY id"
|
||||
)
|
||||
]
|
||||
if not rows or len({row["name"] for row in rows}) != len(rows):
|
||||
raise ValueError("Invalid or duplicate catalogue")
|
||||
document = {
|
||||
"schema": 1,
|
||||
"source_sha256": hashlib.sha256(source.read_bytes()).hexdigest(),
|
||||
"plants": rows,
|
||||
}
|
||||
return (
|
||||
json.dumps(document, ensure_ascii=False, sort_keys=True, indent=2)
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def main():
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--source", type=Path,
|
||||
default=root / "legacy/zhenxun_plugin_farm/resource/db/plant.db",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output", type=Path,
|
||||
default=root / "src/farm_core/data/plants.json",
|
||||
)
|
||||
parser.add_argument("--check", action="store_true")
|
||||
args = parser.parse_args()
|
||||
content = export(args.source)
|
||||
if args.check:
|
||||
if not args.output.is_file() or args.output.read_bytes() != content:
|
||||
raise SystemExit("Catalogue differs; regenerate it explicitly")
|
||||
else:
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_bytes(content)
|
||||
print("Catalogue checked" if args.check else "Catalogue exported")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,74 @@
|
||||
"""可复现复制旧绘图素材;不修改图片、不包含字体或玩家数据。"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
from io import BytesIO
|
||||
import json
|
||||
from pathlib import Path
|
||||
from zipfile import ZIP_STORED, ZipFile, ZipInfo
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def export(root):
|
||||
legacy = root / "legacy/zhenxun_plugin_farm"
|
||||
source = legacy / "resource"
|
||||
paths = list((source / "plant").rglob("*.png"))
|
||||
paths += [source / f"soil/{name}.png" for name in (
|
||||
"草土地", "普通土地",
|
||||
)]
|
||||
paths += [source / "background/expansion.png"]
|
||||
paths += [source / "background/ripe.png"]
|
||||
paths += [source / (
|
||||
f"background/decorateIcon/decorate_{theme}_1.jpg"
|
||||
) for theme in ("default", "spring", "summer", "autumn", "winter")]
|
||||
layout = json.loads((legacy / "config/soil.json").read_bytes())
|
||||
catalogue = json.loads((
|
||||
root / "src/farm_core/data/plants.json"
|
||||
).read_bytes())
|
||||
metadata = {
|
||||
"schema": 1, "canvas": [3072, 1872], "soil_size": layout["size"],
|
||||
"positions": [layout["soil"][str(i)] for i in range(1, 31)],
|
||||
"plants": {row["name"]: {
|
||||
key: row[key] for key in (
|
||||
"phase", "general", "offsetX", "offsetY", "offsetW", "offsetH",
|
||||
)
|
||||
} for row in catalogue["plants"]}, "images": {},
|
||||
}
|
||||
output = BytesIO()
|
||||
with ZipFile(output, "w", compression=ZIP_STORED) as bundle:
|
||||
for path in sorted(paths):
|
||||
name = path.relative_to(source).as_posix()
|
||||
content = path.read_bytes()
|
||||
with Image.open(BytesIO(content)) as image:
|
||||
size = image.size
|
||||
metadata["images"][name] = {
|
||||
"size": size, "sha256": hashlib.sha256(content).hexdigest(),
|
||||
}
|
||||
bundle.writestr(ZipInfo(name, (2020, 1, 1, 0, 0, 0)), content)
|
||||
return output.getvalue(), (
|
||||
json.dumps(metadata, ensure_ascii=False, sort_keys=True, indent=2)
|
||||
+ "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def main():
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--check", action="store_true")
|
||||
args = parser.parse_args()
|
||||
files = zip(("assets.zip", "metadata.json"), export(root))
|
||||
destination = root / "src/farm_render/data"
|
||||
for name, content in files:
|
||||
path = destination / name
|
||||
if args.check:
|
||||
if not path.is_file() or path.read_bytes() != content:
|
||||
raise SystemExit(f"Render asset differs: {name}")
|
||||
else:
|
||||
destination.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(content)
|
||||
print("Render assets checked" if args.check else "Render assets exported")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,108 @@
|
||||
"""离线样例与本机测量,不访问真实玩家库或平台。"""
|
||||
|
||||
import argparse
|
||||
from dataclasses import replace
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import sys
|
||||
from time import perf_counter
|
||||
|
||||
from PIL import __version__ as pillow_version, features
|
||||
|
||||
from farm_core import Catalogue, Identity
|
||||
from farm_core.domain import Account, Plot, Snapshot
|
||||
from farm_render import FarmRenderer, RenderOptions
|
||||
|
||||
|
||||
def demonstration():
|
||||
catalogue = Catalogue.bundled()
|
||||
plants = [p for p in catalogue if not p.name.startswith((
|
||||
"一星", "二星", "三星", "四星", "五星",
|
||||
))]
|
||||
now = 36000
|
||||
plots = []
|
||||
for index in range(1, 19):
|
||||
plant = plants[(index - 1) % min(len(plants), 20)]
|
||||
if index % 6 == 0:
|
||||
plots.append(Plot(index))
|
||||
continue
|
||||
mature_at = now - 10 if index % 3 == 0 else (
|
||||
now + plant.growth_seconds // 3
|
||||
)
|
||||
plot = Plot(index, plant, mature_at)
|
||||
if index == 11:
|
||||
plot = replace(plot, harvest_count=plant.crop)
|
||||
plots.append(plot)
|
||||
return Snapshot(
|
||||
Account("舒影的四季农场", 12580, 36, 3420), (), tuple(plots),
|
||||
), now
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--font", type=Path, required=True)
|
||||
parser.add_argument(
|
||||
"--output", type=Path, default=Path("runtime/render-demo"),
|
||||
)
|
||||
parser.add_argument("--report", type=Path)
|
||||
args = parser.parse_args()
|
||||
args.output.mkdir(parents=True, exist_ok=True)
|
||||
logging.basicConfig(
|
||||
filename=args.output / "render.log", encoding="utf-8",
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s %(name)s %(message)s",
|
||||
)
|
||||
who = Identity("demo", "local", "local", "synthetic-farmer")
|
||||
snapshot, now = demonstration()
|
||||
rows = []
|
||||
for width, quality in ((1024, 75), (1536, 85)):
|
||||
for codec in ("WEBP", "JPEG", "PNG"):
|
||||
if codec == "WEBP" and not features.check("webp"):
|
||||
continue
|
||||
renderer = FarmRenderer(args.font)
|
||||
options = RenderOptions(
|
||||
width=width, quality=quality, format=codec, theme="spring",
|
||||
)
|
||||
started = perf_counter()
|
||||
result = renderer.render(who, snapshot, now, options)
|
||||
cold_ms = (perf_counter() - started) * 1000
|
||||
started = perf_counter()
|
||||
warm = renderer.render(who, snapshot, now, options)
|
||||
warm_ms = (perf_counter() - started) * 1000
|
||||
suffix = {"WEBP":"webp", "JPEG":"jpg", "PNG":"png"}[codec]
|
||||
output = args.output / f"farm-{width}.{suffix}"
|
||||
output.write_bytes(result.content)
|
||||
rows.append({
|
||||
"width": width, "height": result.height, "codec": codec,
|
||||
"quality": quality, "method": options.method,
|
||||
"bytes": len(result.content), "cold_ms": round(cold_ms, 3),
|
||||
"compose_ms": round(result.compose_ms, 3),
|
||||
"encode_ms": round(result.encode_ms, 3),
|
||||
"warm_ms": round(warm_ms, 3), "warm_cache_hit": warm.cache_hit,
|
||||
"asset_cache_bytes": renderer.assets.cache.used_bytes,
|
||||
"result_cache_bytes": renderer.results.used_bytes,
|
||||
})
|
||||
metadata = renderer.assets.metadata
|
||||
missing = sum(
|
||||
f"plant/{name}/{stage}.png" not in metadata["images"]
|
||||
for name, info in metadata["plants"].items()
|
||||
for stage in range(1, len(dict.fromkeys(info["phase"].split(",")))+1)
|
||||
)
|
||||
report = {
|
||||
"python": platform.python_version(), "os": platform.system(),
|
||||
"pillow": pillow_version, "synthetic_data": True,
|
||||
"scope": "本机单次样例;非旧版对照,未测上传或低配服务器",
|
||||
"asset_count": len(metadata["images"]),
|
||||
"legacy_missing_stage_references": missing, "measurements": rows,
|
||||
}
|
||||
content = json.dumps(report, ensure_ascii=False, indent=2) + "\n"
|
||||
(args.report or args.output / "benchmark.json").write_text(
|
||||
content, encoding="utf-8",
|
||||
)
|
||||
print(content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,32 @@
|
||||
"""最终离线检查入口,由 Comet 执行并保存正式检查结果。"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
environment = {
|
||||
**os.environ, "PYTHONPATH": str(root / "src"), "PIP_NO_INDEX": "1",
|
||||
"PYTHONIOENCODING": "utf-8",
|
||||
}
|
||||
commands = (
|
||||
[sys.executable, "scripts/check_baseline.py"],
|
||||
[sys.executable, "scripts/export_catalogue.py", "--check"],
|
||||
[sys.executable, "scripts/export_render_assets.py", "--check"],
|
||||
[
|
||||
sys.executable, "-m", "unittest", "discover",
|
||||
"-s", "tests", "-p", "test_*.py", "-v",
|
||||
],
|
||||
)
|
||||
for command in commands:
|
||||
result = subprocess.run(command, cwd=root, env=environment)
|
||||
if result.returncode:
|
||||
return result.returncode
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user