mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-10-29 05:29:58 +08:00
fixed # 修复作物资源位置不对的历史遗留BUG
add # 新增作物会生虫的逻辑
This commit is contained in:
@@ -10,6 +10,17 @@ from zipfile import ZIP_STORED, ZipFile, ZipInfo
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def apply_positions(root, metadata):
|
||||
document = json.loads((
|
||||
root / "src/farm_render/data/crop_positions.json"
|
||||
).read_bytes())
|
||||
if document.get("schema") != 2:
|
||||
raise ValueError("Unsupported crop position schema")
|
||||
if document.get("mode") != "full_canvas_bottom_center":
|
||||
raise ValueError("Unsupported crop placement mode")
|
||||
metadata["placement"] = document
|
||||
|
||||
|
||||
def export(root):
|
||||
legacy = root / "legacy/zhenxun_plugin_farm"
|
||||
source = legacy / "resource"
|
||||
@@ -19,6 +30,8 @@ def export(root):
|
||||
)]
|
||||
paths += [source / "background/expansion.png"]
|
||||
paths += [source / "background/ripe.png"]
|
||||
paths += [source / "background/userInfo.png"]
|
||||
paths += [source / "background/frame/frame.png"]
|
||||
paths += [source / (
|
||||
f"background/decorateIcon/decorate_{theme}_1.jpg"
|
||||
) for theme in ("default", "spring", "summer", "autumn", "winter")]
|
||||
@@ -36,19 +49,29 @@ def export(root):
|
||||
} for row in catalogue["plants"]}, "images": {},
|
||||
}
|
||||
output = BytesIO()
|
||||
webgl_source = root / "src/farm_render/source_assets"
|
||||
manifest = json.loads((webgl_source / "manifest.json").read_bytes())
|
||||
metadata["pest"] = manifest["pest"]
|
||||
entries = {path.relative_to(source).as_posix(): path for path in paths}
|
||||
entries.update({name: webgl_source / name for name in manifest["images"]})
|
||||
with ZipFile(output, "w", compression=ZIP_STORED) as bundle:
|
||||
for path in sorted(paths):
|
||||
name = path.relative_to(source).as_posix()
|
||||
for name, path in sorted(entries.items()):
|
||||
content = path.read_bytes()
|
||||
with Image.open(BytesIO(content)) as image:
|
||||
size = image.size
|
||||
metadata["images"][name] = {
|
||||
"size": size, "sha256": hashlib.sha256(content).hexdigest(),
|
||||
}
|
||||
if name in manifest["images"]:
|
||||
expected = manifest["images"][name]
|
||||
if (list(size) != expected["size"] or
|
||||
metadata["images"][name]["sha256"] != expected["sha256"]):
|
||||
raise ValueError("Captured WebGL sprite changed")
|
||||
item = ZipInfo(name, (2020, 1, 1, 0, 0, 0))
|
||||
item.create_system = 3
|
||||
item.external_attr = 0o100644 << 16
|
||||
bundle.writestr(item, content)
|
||||
apply_positions(root, metadata)
|
||||
return output.getvalue(), (
|
||||
json.dumps(metadata, ensure_ascii=False, sort_keys=True, indent=2)
|
||||
+ "\n"
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"""从已下载的公开 Unity 场景包提取图片;不执行游戏脚本。"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def extract(bundle, destination):
|
||||
import UnityPy
|
||||
|
||||
environment = UnityPy.load(str(bundle))
|
||||
destination.mkdir(parents=True, exist_ok=True)
|
||||
images = {}
|
||||
for obj in environment.objects:
|
||||
if obj.type.name != "Sprite":
|
||||
continue
|
||||
sprite = obj.read()
|
||||
name = sprite.m_Name
|
||||
if re.fullmatch(r"pest\d+", name):
|
||||
path = f"pest/{name}.png"
|
||||
elif name in ("Icon_PlantSeed", "Icon_PlantWither"):
|
||||
path = "webgl/" + (
|
||||
"seed.png" if name == "Icon_PlantSeed" else "wither.png"
|
||||
)
|
||||
else:
|
||||
continue
|
||||
image = sprite.image
|
||||
if image.size != (round(sprite.m_Rect.width),
|
||||
round(sprite.m_Rect.height)):
|
||||
raise ValueError("Sprite extraction lost its full canvas")
|
||||
target = destination / path
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
image.save(target)
|
||||
images[path] = {
|
||||
"size": list(image.size),
|
||||
"sha256": hashlib.sha256(target.read_bytes()).hexdigest(),
|
||||
}
|
||||
if len(images) != 40:
|
||||
raise ValueError("Expected 38 pest frames and two common crop sprites")
|
||||
manifest = {
|
||||
"schema": 1,
|
||||
"source_url": (
|
||||
"https://game.qqnc.qq.com/happyfarm/unity/Assets/AssetBundles/"
|
||||
"WebGL/mainscene_053483f584f07436ac0569e2f4f7ed01"
|
||||
),
|
||||
"source_sha256": hashlib.sha256(bundle.read_bytes()).hexdigest(),
|
||||
"extractor": "UnityPy 1.25.4 Sprite.image; full sprite rect checked",
|
||||
"images": images,
|
||||
"pest": {
|
||||
"frames": [f"pest/pest{i}.png" for i in range(1, 76, 2)],
|
||||
"sample_rate": 24,
|
||||
"loop": True,
|
||||
"display_size": [22, 30],
|
||||
"pivot": [0.5, 0.5],
|
||||
"positions": [[-26, 13], [-5, 42], [24, 24]],
|
||||
},
|
||||
}
|
||||
(destination / "manifest.json").write_text(
|
||||
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
print(f"Extracted {len(images)} images")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("bundle", type=Path)
|
||||
parser.add_argument("--dependencies", type=Path)
|
||||
parser.add_argument("--output", type=Path, default=(
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "src/farm_render/source_assets"
|
||||
))
|
||||
args = parser.parse_args()
|
||||
if args.dependencies:
|
||||
sys.path.insert(0, str(args.dependencies))
|
||||
extract(args.bundle, args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,93 @@
|
||||
"""使用正式渲染器离线绘制作物统一公式与虫子预览。"""
|
||||
|
||||
import argparse
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
from ci_font import choose_font
|
||||
from farm_core import Catalogue, Identity
|
||||
from farm_core.domain import Account, Plot, Snapshot
|
||||
from farm_render import FarmRenderer, RenderOptions
|
||||
from farm_render.canvas import Canvas
|
||||
|
||||
|
||||
def preview(renderer, output, width, names):
|
||||
scale = width / 3072
|
||||
native = lambda n: max(1, round(n * scale))
|
||||
cell_w, cell_h = 360, 440
|
||||
sheet = Image.new("RGB", (cell_w * 6, 95 + len(names) * (cell_h+45)),
|
||||
"#edf0eb")
|
||||
draw = ImageDraw.Draw(sheet)
|
||||
title_font = ImageFont.truetype(str(renderer.font_path), 28)
|
||||
text_font = ImageFont.truetype(str(renderer.font_path), 21)
|
||||
draw.text((20, 12), f"正式渲染器 · 统一定位与虫子 / 输出档位 {width}",
|
||||
font=title_font, fill="#172b20")
|
||||
draw.text((20, 53), "使用包内完整图片;红线为生长图片对齐基线;"
|
||||
"虫子使用原版固定坐标。", font=text_font, fill="#344c3f")
|
||||
who = Identity("preview", "local", "qq", "formula")
|
||||
old_position = renderer.assets.metadata["positions"][0]
|
||||
renderer.assets.metadata["positions"][0] = {"x": 63, "y": 220}
|
||||
try:
|
||||
for row, name in enumerate(names):
|
||||
plant = Catalogue.bundled().get(name)
|
||||
info = renderer.assets.metadata["plants"][name]
|
||||
phases = tuple(dict.fromkeys(map(int, info["phase"].split(","))))
|
||||
times = (0,) + phases
|
||||
top = 95 + row * (cell_h + 45)
|
||||
draw.text((20, top), name, font=text_font, fill="#172b20")
|
||||
data = Snapshot(Account("公式预览", 0, 0, 0), (), (
|
||||
Plot(1, plant, plant.growth_seconds,
|
||||
pest_count=3, pest_spawned=3),
|
||||
))
|
||||
for stage, now in enumerate(times):
|
||||
scene = renderer.scene(who, data, now)
|
||||
view = scene.plots[0]
|
||||
if stage == 0:
|
||||
view = replace(view, pest_count=0, pest_frames=())
|
||||
scene = replace(scene, plots=(view,))
|
||||
canvas = Canvas.new((native(cell_w), native(cell_h)), "#edf0eb")
|
||||
soil = renderer.assets.image(
|
||||
"soil/普通土地.png", (native(234), native(156)),
|
||||
)
|
||||
canvas.paste(soil, (native(63), native(220)))
|
||||
renderer._plants(canvas, scene, RenderOptions(width=width))
|
||||
renderer._pests(canvas, scene, RenderOptions(width=width))
|
||||
painter = ImageDraw.Draw(canvas.image)
|
||||
painter.line((0, native(376), native(360), native(376)),
|
||||
fill="#bd4545", width=1)
|
||||
left = stage * cell_w
|
||||
draw.text((left+12, top+32), "公共种子" if stage == 0 else
|
||||
f"生长阶段{stage} · 3只虫", font=text_font,
|
||||
fill="#172b20")
|
||||
sheet.paste(canvas.image, (
|
||||
left + (cell_w-canvas.image.width)//2, top+65,
|
||||
))
|
||||
finally:
|
||||
renderer.assets.metadata["positions"][0] = old_position
|
||||
path = output / f"crop-formula-{width}.png"
|
||||
sheet.save(path)
|
||||
return path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--font", type=Path)
|
||||
parser.add_argument("--plants", nargs="+", default=(
|
||||
"草莓", "橙子", "樱桃", "黄瓜", "桂圆",
|
||||
))
|
||||
args = parser.parse_args()
|
||||
args.output.mkdir(parents=True, exist_ok=True)
|
||||
renderer = FarmRenderer(args.font or choose_font())
|
||||
for width in (1024, 1536, 3072):
|
||||
print(preview(renderer, args.output, width, args.plants))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user