#!/usr/bin/env python3 """Generate the Cocos Creator 3.8 Stage 1 project assets. All layout numbers come from source_export/stage1_world_camera_source.json. Stage 1B land nodes also read source_export/stage1_visible_lands_source.json. Stage 1C empty-land interaction nodes read source_export/stage1_land_interaction_source.json. Stage 1D top HUD nodes read source_export/stage1_top_hud_source.json. The script writes Cocos Creator assets under CocosFarm/assets and leaves Creator cache folders alone so the reconstruction remains reproducible. """ from __future__ import annotations import copy import json import shutil import uuid from pathlib import Path from PIL import Image ROOT = Path(__file__).resolve().parents[1] PROJECT = ROOT / "CocosFarm" ASSETS = PROJECT / "assets" SPINE_DIR = ASSETS / "spine" / "stage1" SOURCE_JSON = ROOT / "source_export" / "stage1_world_camera_source.json" LAND_SOURCE_JSON = ROOT / "source_export" / "stage1_visible_lands_source.json" INTERACTION_SOURCE_JSON = ROOT / "source_export" / "stage1_land_interaction_source.json" DECOR_SOURCE_JSON = ROOT / "source_export" / "stage1_scene_decor_source.json" TOP_HUD_SOURCE_JSON = ROOT / "source_export" / "stage1_top_hud_source.json" RULES_PATH = ROOT / "source_export" / "cocos_creator38_conversion_rules.md" SCENE_TEMPLATE = Path( "/Applications/Cocos/Creator/3.8.8/CocosCreator.app/Contents/Resources/" "resources/3d/engine/editor/assets/default_file_content/scene/scene-2d.scene" ) SCENE_UUID = "02bb8b9c-d1f7-40d3-b9d3-3568d015c285" SOURCE_WORLD_LAYER = 1073741824 SOURCE_UI_LAYER = 33554432 CAMERA_VISIBILITY = 1083179010 NAMESPACE = uuid.UUID("4799205f-8960-4807-a28e-9e32ad821936") COCOS_BASE64_KEYS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" def stable_uuid(name: str) -> str: return str(uuid.uuid5(NAMESPACE, f"stage1-world-camera:{name}")) def compress_uuid(uuid_text: str) -> str: hex_text = uuid_text.replace("-", "") result = hex_text[:5] index = 5 while index < len(hex_text): first = int(hex_text[index], 16) second = int(hex_text[index + 1], 16) if index + 1 < len(hex_text) else 0 third = int(hex_text[index + 2], 16) if index + 2 < len(hex_text) else 0 result += COCOS_BASE64_KEYS[(first << 2) | (second >> 2)] result += COCOS_BASE64_KEYS[((second & 3) << 4) | third] index += 3 return result def script_class_id(relative_asset_path: str) -> str: return compress_uuid(stable_uuid(f"script:assets/{relative_asset_path}")) def read_json(path: Path) -> dict | list: return json.loads(path.read_text(encoding="utf-8")) def write_json(path: Path, data: dict | list) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") def write_text(path: Path, text: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(text, encoding="utf-8") def ensure_directory_meta(path: Path) -> None: path.mkdir(parents=True, exist_ok=True) if path == ASSETS: return meta = { "ver": "1.2.0", "importer": "directory", "imported": True, "uuid": stable_uuid(f"dir:{path.relative_to(PROJECT)}"), "files": [], "subMetas": {}, "userData": {"compressionType": {}, "isRemoteBundle": {}}, } write_json(path.with_suffix(path.suffix + ".meta"), meta) def vec2(x: float, y: float) -> dict: return {"__type__": "cc.Vec2", "x": x, "y": y} def vec3(x: float, y: float, z: float) -> dict: return {"__type__": "cc.Vec3", "x": x, "y": y, "z": z} def quat_identity() -> dict: return {"__type__": "cc.Quat", "x": 0, "y": 0, "z": 0, "w": 1} def color(r: int, g: int, b: int, a: int) -> dict: return {"__type__": "cc.Color", "r": r, "g": g, "b": b, "a": a} def size(width: float, height: float) -> dict: return {"__type__": "cc.Size", "width": width, "height": height} def ref(index: int) -> dict: return {"__id__": index} def uuid_ref(asset_uuid: str, expected_type: str | None = None) -> dict: result = {"__uuid__": asset_uuid} if expected_type: result["__expectedType__"] = expected_type return result def node( name: str, parent: int | None, children: list[int], components: list[int], position: dict, active: bool = True, layer: int = SOURCE_WORLD_LAYER, id_key: str | None = None, ) -> dict: return { "__type__": "cc.Node", "_name": name, "_objFlags": 0, "_parent": None if parent is None else ref(parent), "_children": [ref(item) for item in children], "_active": active, "_components": [ref(item) for item in components], "_prefab": None, "_lpos": position, "_lrot": quat_identity(), "_lscale": vec3(1, 1, 1), "_layer": layer, "_euler": vec3(0, 0, 0), "_id": stable_uuid(f"node:{id_key or name}:{position['x']}:{position['y']}:{position['z']}"), } def ui_transform(node_index: int, width: float, height: float, anchor_x: float = 0.5, anchor_y: float = 0.5) -> dict: return { "__type__": "cc.UITransform", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_priority": 0, "_contentSize": size(width, height), "_anchorPoint": vec2(anchor_x, anchor_y), "_id": stable_uuid(f"uitransform:{node_index}:{width}:{height}:{anchor_x}:{anchor_y}"), } def camera_component( node_index: int, source_camera: dict, *, priority: int | None = None, clear_flags: int | None = None, component_id: str = "component:main-camera", ) -> dict: return { "__type__": "cc.Camera", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_projection": int(source_camera["projection"]), "_priority": int(source_camera.get("priority", priority if priority is not None else 0)), "_fov": 45, "_fovAxis": 0, "_orthoHeight": float(source_camera["orthographicHeight"]), "_near": float(source_camera["near"]), "_far": float(source_camera["far"]), "_color": color(0, 0, 0, 255), "_depth": 1, "_stencil": 0, "_clearFlags": int(source_camera.get("clearFlags", clear_flags if clear_flags is not None else 7)), "_rect": {"__type__": "cc.Rect", "x": 0, "y": 0, "width": 1, "height": 1}, "_aperture": 19, "_shutter": 7, "_iso": 0, "_screenScale": 1, "_visibility": int(source_camera["visibility"]), "_targetTexture": None, "_id": stable_uuid(component_id), } def canvas_component(node_index: int, camera_index: int) -> dict: return { "__type__": "cc.Canvas", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_cameraComponent": ref(camera_index), "_alignCanvasWithScreen": True, "_id": stable_uuid("component:canvas"), } def widget_component(node_index: int) -> dict: return { "__type__": "cc.Widget", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_alignFlags": 45, "_target": None, "_left": 0, "_right": 0, "_top": 0, "_bottom": 0, "_horizontalCenter": 0, "_verticalCenter": 0, "_isAbsLeft": True, "_isAbsRight": True, "_isAbsTop": True, "_isAbsBottom": True, "_isAbsHorizontalCenter": True, "_isAbsVerticalCenter": True, "_originalWidth": 0, "_originalHeight": 0, "_alignMode": 2, "_lockFlags": 0, "_id": stable_uuid("component:root-widget"), } def source_widget_component(node_index: int, widget_source: dict, id_key: str) -> dict: # Cocos compact scenes omit fields that equal engine defaults. Widget's # omitted `_alignMode` defaults to ON_WINDOW_RESIZE in Cocos Creator 3.8. return { "__type__": "cc.Widget", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_alignFlags": int(widget_source["alignFlags"]), "_target": None, "_left": float(widget_source.get("left") or 0), "_right": float(widget_source.get("right") or 0), "_top": float(widget_source.get("top") or 0), "_bottom": float(widget_source.get("bottom") or 0), "_horizontalCenter": 0, "_verticalCenter": 0, "_isAbsLeft": bool(widget_source.get("isAbsLeft", True)), "_isAbsRight": bool(widget_source.get("isAbsRight", True)), "_isAbsTop": bool(widget_source.get("isAbsTop", True)), "_isAbsBottom": bool(widget_source.get("isAbsBottom", True)), "_isAbsHorizontalCenter": bool(widget_source.get("isAbsHorizontalCenter", True)), "_isAbsVerticalCenter": bool(widget_source.get("isAbsVerticalCenter", True)), "_originalWidth": float(widget_source.get("originalWidth") or 0), "_originalHeight": float(widget_source.get("originalHeight") or 0), "_alignMode": int(widget_source.get("alignMode", 2)), "_lockFlags": 0, "_id": stable_uuid(f"widget:{id_key}:{widget_source['alignFlags']}:{widget_source.get('sourceTemplate')}"), } def sprite_component( node_index: int, sprite_frame_uuid: str, sprite_type: int = 0, size_mode: int = 0, ) -> dict: return { "__type__": "cc.Sprite", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "_srcBlendFactor": 2, "_dstBlendFactor": 4, "_color": color(255, 255, 255, 255), "_sharedMaterial": None, "_spriteFrame": {"__uuid__": sprite_frame_uuid}, "_type": sprite_type, "_fillType": 0, "_sizeMode": size_mode, "_fillCenter": vec2(0, 0), "_fillStart": 0, "_fillRange": 0, "_isTrimmedMode": True, "_useGrayscale": False, "_atlas": None, "_id": stable_uuid(f"component:sprite:{node_index}:{sprite_frame_uuid}"), } def uint32_to_color(value: int) -> dict: # Cocos compact JSON stores label colors in the same packed order as Color.toUint32. return color(value & 255, (value >> 8) & 255, (value >> 16) & 255, (value >> 24) & 255) def label_component(node_index: int, label: dict) -> dict: return { "__type__": "cc.Label", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_customMaterial": None, "_srcBlendFactor": 2, "_dstBlendFactor": 4, "_color": uint32_to_color(int(label["colorUint32"])), "_string": str(label["string"]), "_horizontalAlign": int(label.get("horizontalAlign", 1)), "_verticalAlign": int(label.get("verticalAlign", 1)), "_actualFontSize": float(label["actualFontSize"]), "_fontSize": float(label["fontSize"]), "_fontFamily": "Arial", "_lineHeight": float(label["lineHeight"]), "_overflow": int(label.get("overflow", 0)), "_enableWrapText": bool(label.get("enableWrapText", True)), "_font": None, "_isSystemFontUsed": True, "_spacingX": 0, "_isBold": bool(label["bold"]), "_isItalic": False, "_isUnderline": False, "_underlineHeight": 2, "_cacheMode": 0, "_id": stable_uuid(f"component:label:{node_index}:{label['string']}"), } def mask_component(node_index: int, mask: dict) -> dict: return { "__type__": "cc.Mask", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "_srcBlendFactor": 2, "_dstBlendFactor": 4, "_color": color(255, 255, 255, 255), "_sharedMaterial": None, "_type": int(mask["type"]), "_segments": int(mask["segments"]), "_id": stable_uuid(f"component:mask:{node_index}:{mask['type']}:{mask['segments']}"), "__prefab": None, } def graphics_component(node_index: int, graphics: dict) -> dict: fill = uint32_to_color(int(graphics["fillColorUint32"])) return { "__type__": "cc.Graphics", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_visFlags": 0, "_customMaterial": None, "_srcBlendFactor": 2, "_dstBlendFactor": 4, "_color": color(255, 255, 255, 255), "_lineWidth": 1, "_strokeColor": color(0, 0, 0, 255), "_lineJoin": 2, "_lineCap": 0, "_fillColor": fill, "_miterLimit": 10, "_id": stable_uuid(f"component:graphics:{node_index}:{graphics['fillColorUint32']}"), } def progress_bar_component(node_index: int, progress_bar: dict, progress: float) -> dict: return { "__type__": "cc.ProgressBar", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "_barSprite": None, "_mode": 0, "_totalLength": float(progress_bar["totalLength"]), "_progress": float(progress), "_reverse": False, "_id": stable_uuid(f"component:progress-bar:{node_index}:{progress_bar['totalLength']}"), "__prefab": None, } def layout_component(node_index: int, layout: dict) -> dict: return { "__type__": "cc.Layout", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_resizeMode": int(layout.get("resizeMode", 0)), "_layoutType": int(layout.get("layoutType", 0)), "_cellSize": size(40, 40), "_startAxis": 0, "_paddingLeft": float(layout.get("paddingLeft", 0)), "_paddingRight": float(layout.get("paddingRight", 0)), "_paddingTop": float(layout.get("paddingTop", 0)), "_paddingBottom": float(layout.get("paddingBottom", 0)), "_spacingX": float(layout.get("spacingX", 0)), "_spacingY": 0, "_verticalDirection": 1, "_horizontalDirection": int(layout.get("horizontalDirection", 0)), "_constraint": 0, "_constraintNum": 2, "_affectedByScale": bool(layout.get("affectedByScale", False)), "_isAlign": bool(layout.get("isAlign", False)), "_id": stable_uuid(f"component:layout:{node_index}:{layout.get('sourceNode', '')}"), } def stage1_land_interaction_component(node_index: int) -> dict: return { "__type__": script_class_id("scripts/stage1/Stage1LandInteraction.ts"), "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_id": stable_uuid("component:stage1-land-interaction"), } def stage1_world_camera_component(node_index: int, camera_component_index: int) -> dict: return { "__type__": script_class_id("scripts/stage1/Stage1WorldCamera.ts"), "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "mainCamera": ref(camera_component_index), "_id": stable_uuid("component:stage1-world-camera"), } def stage1_top_hud_component(node_index: int) -> dict: return { "__type__": script_class_id("scripts/stage1/Stage1TopHud.ts"), "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_id": stable_uuid("component:stage1-top-hud"), } def stage1_ui_camera_viewport_component(node_index: int) -> dict: return { "__type__": script_class_id("scripts/stage1/Stage1UiCameraViewport.ts"), "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_id": stable_uuid("component:stage1-ui-camera-viewport"), } def scene_land_spine_uuids() -> dict[str, str]: return { "skeletonDataUuid": stable_uuid("spine:scene_land.json"), "atlasUuid": stable_uuid("spine:scene_land.atlas"), "imageUuid": stable_uuid("spine:scene_land.png"), } def decor_spine_uuids(asset_id: str) -> dict[str, str]: return { "skeletonDataUuid": stable_uuid(f"spine:{asset_id}.skel"), "atlasUuid": stable_uuid(f"spine:{asset_id}.atlas"), "imageUuid": stable_uuid(f"spine:{asset_id}.png"), } def spine_skeleton_component(node_index: int, skeleton_data_uuid: str, default_skin: str, default_animation: str) -> dict: return { "__type__": "sp.Skeleton", "_name": "", "_objFlags": 0, "node": ref(node_index), "_enabled": True, "__prefab": None, "_materials": [], "_srcBlendFactor": 2, "_dstBlendFactor": 4, "_color": color(255, 255, 255, 255), "_skeletonData": uuid_ref(skeleton_data_uuid, "sp.SkeletonData"), "defaultSkin": default_skin, "defaultAnimation": default_animation, "_premultipliedAlpha": True, "loop": False, "_timeScale": 1, "_preCacheMode": 1, "_cacheMode": 1, "_sockets": [], "_useTint": False, "_debugMesh": False, "_debugBones": False, "_debugSlots": False, "_enableBatch": True, "_id": stable_uuid(f"component:spine-skeleton:{node_index}:{default_skin}:{default_animation}"), } def remap_ids(value: object, mapping: dict[int, int]) -> object: if isinstance(value, dict): if set(value.keys()) == {"__id__"} and value["__id__"] in mapping: return {"__id__": mapping[value["__id__"]]} return {key: remap_ids(item, mapping) for key, item in value.items()} if isinstance(value, list): return [remap_ids(item, mapping) for item in value] return value def image_meta(name: str, image_uuid: str, width: int, height: int, sprite_frame: dict | None = None) -> dict: texture_uuid = f"{image_uuid}@6c48a" sprite_uuid = f"{image_uuid}@f9941" if sprite_frame: frame_rect = sprite_frame["rect"] original_size = sprite_frame["originalSize"] offset = sprite_frame.get("offset", {"x": 0, "y": 0}) pivot = sprite_frame.get("pivot", {"x": 0.5, "y": 0.5}) cap = sprite_frame.get("capInsets", [0, 0, 0, 0]) frame_width = int(frame_rect["width"]) frame_height = int(frame_rect["height"]) raw_width = int(original_size["width"]) raw_height = int(original_size["height"]) else: offset = {"x": 0, "y": 0} pivot = {"x": 0.5, "y": 0.5} cap = [0, 0, 0, 0] frame_width = width frame_height = height raw_width = width raw_height = height return { "ver": "1.0.27", "importer": "image", "imported": True, "uuid": image_uuid, "files": [".json", ".png"], "subMetas": { "6c48a": { "ver": "1.0.22", "importer": "texture", "uuid": texture_uuid, "imported": True, "files": [".json"], "subMetas": {}, "userData": { "wrapModeS": "clamp-to-edge", "wrapModeT": "clamp-to-edge", "minfilter": "linear", "magfilter": "linear", "mipfilter": "none", "premultiplyAlpha": False, "anisotropy": 0, "isUuid": True, "imageUuidOrDatabaseUri": image_uuid, "visible": False, }, "displayName": name, "id": "6c48a", "name": "texture", }, "f9941": { "ver": "1.0.12", "importer": "sprite-frame", "uuid": sprite_uuid, "imported": True, "files": [".json"], "subMetas": {}, "userData": { "wrapModeS": "clamp-to-edge", "wrapModeT": "clamp-to-edge", "minfilter": "linear", "magfilter": "linear", "premultiplyAlpha": False, "generateMipmap": False, "anisotropy": 1, "trimType": "custom", "trimThreshold": 1, "rotated": False, "offsetX": float(offset["x"]), "offsetY": float(offset["y"]), "trimX": 0, "trimY": 0, "width": frame_width, "height": frame_height, "rawWidth": raw_width, "rawHeight": raw_height, "borderTop": int(cap[0]), "borderBottom": int(cap[2]), "borderLeft": int(cap[3]), "borderRight": int(cap[1]), "isUuid": True, "imageUuidOrDatabaseUri": texture_uuid, "atlasUuid": "", "mipfilter": "none", "packable": True, "vertices": { "rawPosition": [ -frame_width / 2, -frame_height / 2, 0, frame_width / 2, -frame_height / 2, 0, -frame_width / 2, frame_height / 2, 0, frame_width / 2, frame_height / 2, 0, ], "indexes": [0, 1, 2, 2, 1, 3], "uv": [0, frame_height, frame_width, frame_height, 0, 0, frame_width, 0], "nuv": [0, 0, 1, 0, 0, 1, 1, 1], "minPos": [-frame_width / 2, -frame_height / 2, 0], "maxPos": [frame_width / 2, frame_height / 2, 0], }, "pixelsToUnit": 100, "pivotX": float(pivot["x"]), "pivotY": float(pivot["y"]), "meshType": 0, }, "displayName": name, "id": "f9941", "name": "spriteFrame", }, }, "userData": { "type": "sprite-frame", "redirect": texture_uuid, "hasAlpha": True, "fixAlphaTransparencyArtifacts": False, }, } def write_script_assets(land_evidence: dict, interaction_evidence: dict, top_hud_evidence: dict) -> None: core_dir = ASSETS / "scripts" / "core" stage_dir = ASSETS / "scripts" / "stage1" mock_dir = ASSETS / "scripts" / "mock" ensure_directory_meta(ASSETS / "scripts") ensure_directory_meta(core_dir) ensure_directory_meta(stage_dir) ensure_directory_meta(mock_dir) source_space = """export interface SourceVec2 { x: number; y: number; } export interface SourceVec3 extends SourceVec2 { z: number; } export interface SourceNodeTransform { position: SourceVec3; scale: SourceVec3; euler: SourceVec3; size: SourceVec2; pivot: SourceVec2; } export interface CreatorNodeTransform extends SourceNodeTransform { layer: number; } export interface SourceCameraState { position: SourceVec3; orthographicHeight: number; near: number; far: number; visibility: number; } export const SOURCE_DESIGN_WIDTH = 720; export const SOURCE_DESIGN_HEIGHT = 1280; export const SOURCE_WORLD_LAYER = 1073741824; export const SOURCE_CAMERA_VISIBILITY = 1083179010; export const SOURCE_CAMERA_ORTHO_HEIGHT = 640; export const SOURCE_CAMERA_MIN_ZOOM = 0.8; export const SOURCE_CAMERA_MAX_ZOOM = 2.4; export const SOURCE_CAMERA_MAX_VISIBLE_WIDTH = 2300; export function sourceCameraInitialZoom(): number { return SOURCE_CAMERA_MIN_ZOOM; } export function sourceCameraInitialOrthoHeight(): number { return SOURCE_CAMERA_ORTHO_HEIGHT / sourceCameraInitialZoom(); } export function toCreatorNodeTransform(source: SourceNodeTransform): CreatorNodeTransform { return { position: { ...source.position }, scale: { ...source.scale }, euler: { ...source.euler }, size: { ...source.size }, pivot: { ...source.pivot }, layer: SOURCE_WORLD_LAYER, }; } export function toCreatorCameraState(source: SourceCameraState): SourceCameraState { return { position: { ...source.position }, orthographicHeight: source.orthographicHeight, near: source.near, far: source.far, visibility: source.visibility, }; } """ source_visible_land = """export interface SourceLandAssetRule { landLevel: number; levelName: string; landRes: string; landDryRes: string; } export interface SourceVisibleLand { landId: number; gridName: string; assetId: string; landLevel: number; state: string; } export function resolveLandAssetId(rule: SourceLandAssetRule, dry: boolean): string { const resource = dry ? rule.landDryRes : rule.landRes; const match = resource.match(/model\\/v3\\/(.+?)\\/spriteFrame/); return match ? match[1] : resource; } """ source_land_interaction = """export interface SourceSeedMock { seedItemId: number; cropConfigId: number; name: string; assetId: string; count: number; bundle: string; resource: string; } export interface SourceLandInteractionRule { selectableState: string; noResponseStates: string[]; } """ source_top_hud = """export interface Stage1UserMock { nickname: string; level: number; exp: number; expMax: number; gold: number; goldDisplay: string; diamond: number; avatarAssetId: string; } export function stage1ExpProgress(user: Stage1UserMock): number { if (user.expMax <= 0) { return 0; } return Math.max(0, Math.min(1, user.exp / user.expMax)); } """ stage_script = """import { _decorator, Camera, Component } from 'cc'; import { SOURCE_CAMERA_VISIBILITY, SOURCE_DESIGN_HEIGHT, SOURCE_DESIGN_WIDTH, sourceCameraInitialOrthoHeight, sourceCameraInitialZoom } from '../core/SourceSpace'; const { ccclass, property } = _decorator; @ccclass('Stage1WorldCamera') export class Stage1WorldCamera extends Component { @property(Camera) public mainCamera: Camera | null = null; start() { if (!this.mainCamera) { return; } this.mainCamera.orthoHeight = sourceCameraInitialOrthoHeight(); this.mainCamera.visibility = SOURCE_CAMERA_VISIBILITY; this.mainCamera.camera.update(true); } public sourceDesignSize() { return { width: SOURCE_DESIGN_WIDTH, height: SOURCE_DESIGN_HEIGHT, initialZoom: sourceCameraInitialZoom(), initialOrthoHeight: sourceCameraInitialOrthoHeight(), }; } } """ ui_camera_viewport_script = """import { _decorator, Camera, Component, view } from 'cc'; import { SOURCE_DESIGN_HEIGHT } from '../core/SourceSpace'; const { ccclass } = _decorator; @ccclass('Stage1UiCameraViewport') export class Stage1UiCameraViewport extends Component { private camera: Camera | null = null; onLoad() { this.camera = this.getComponent(Camera); } onEnable() { this.syncToSourceDesignHeight(); view.on('canvas-resize', this.syncToSourceDesignHeight, this); view.on('design-resolution-changed', this.syncToSourceDesignHeight, this); } start() { this.syncToSourceDesignHeight(); } onDisable() { view.off('canvas-resize', this.syncToSourceDesignHeight, this); view.off('design-resolution-changed', this.syncToSourceDesignHeight, this); } private syncToSourceDesignHeight() { if (!this.camera) { return; } // Keep the recovered UI camera in the source Canvas coordinate system. this.camera.orthoHeight = SOURCE_DESIGN_HEIGHT / 2; this.camera.camera.update(true); } } """ first_land_icon = land_evidence["visibleLands"][0]["icon"] source_land_icon_x = float(first_land_icon["localPosition"]["x"]) source_land_icon_y = float(first_land_icon["localPosition"]["y"]) source_land_icon_width = float(first_land_icon["size"]["x"]) source_land_icon_height = float(first_land_icon["size"]["y"]) slot_layout = interaction_evidence["slotLayout"] layout_component_evidence = slot_layout["layoutComponent"] seed_slot_width = float(slot_layout["slotSize"]["x"]) seed_layout_spacing_x = float(layout_component_evidence["spacingX"]) seed_layout_padding_right = float(layout_component_evidence["paddingRight"]) interaction_script = """import { _decorator, Camera, Component, EventTouch, Label, Layout, Node, Sprite, UITransform, Vec3 } from 'cc'; import { STAGE1_VISIBLE_LAND_STATE } from '../mock/Stage1LandMock'; import { STAGE1_SEED_MOCK } from '../mock/Stage1SeedMock'; const { ccclass } = _decorator; @ccclass('Stage1LandInteraction') export class Stage1LandInteraction extends Component { private gridOrigin: Node | null = null; private uiRoot: Node | null = null; private interactionRoot: Node | null = null; private mainCamera: Camera | null = null; private uiCamera: Camera | null = null; private readonly selectableState = 'unlocked_normal'; private readonly sourceLandIconX = __SOURCE_LAND_ICON_X__; private readonly sourceLandIconY = __SOURCE_LAND_ICON_Y__; private readonly sourceLandIconWidth = __SOURCE_LAND_ICON_WIDTH__; private readonly sourceLandIconHeight = __SOURCE_LAND_ICON_HEIGHT__; private readonly seedSlotWidth = __SEED_SLOT_WIDTH__; private readonly seedLayoutSpacingX = __SEED_LAYOUT_SPACING_X__; private readonly seedLayoutPaddingRight = __SEED_LAYOUT_PADDING_RIGHT__; start() { this.gridOrigin = this.findDescendant(this.node, 'GridOrigin'); this.uiRoot = this.findDescendant(this.node, 'ui'); this.interactionRoot = this.findDescendant(this.node, 'plant_interactive_v2'); this.mainCamera = this.findDescendant(this.node, 'Camera')?.getComponent(Camera) ?? null; this.uiCamera = this.findDescendant(this.node, 'uiCamera')?.getComponent(Camera) ?? null; if (!this.gridOrigin || !this.uiRoot || !this.interactionRoot || !this.mainCamera || !this.uiCamera) { return; } this.interactionRoot.active = false; this.bindSeedMock(); this.mountLandDetailBubble(); this.setSelectedLandVisual(false); this.node.off(Node.EventType.TOUCH_END, this.onRootTouchEnd, this); this.node.on(Node.EventType.TOUCH_END, this.onRootTouchEnd, this); for (const land of STAGE1_VISIBLE_LAND_STATE) { const grid = this.gridOrigin.getChildByName(land.gridName); if (!grid) { continue; } grid.off(Node.EventType.TOUCH_END); grid.on(Node.EventType.TOUCH_END, (event: EventTouch) => { this.onLandClick(land.gridName, event); }, this); } } private onLandClick(gridName: string, event: EventTouch) { const resolvedGridName = this.resolveGridNameFromTouch(event, true); if (!resolvedGridName) { return; } const land = STAGE1_VISIBLE_LAND_STATE.find((item) => item.gridName === resolvedGridName); if (!land || land.state !== this.selectableState || !this.gridOrigin || !this.interactionRoot) { return; } event.propagationStopped = true; const grid = this.gridOrigin.getChildByName(resolvedGridName); if (!grid) { return; } const targetPosition = this.gridIconPosition(grid); this.interactionRoot.setPosition(0, 0, 0); this.applySourceTargetPositions(targetPosition); this.interactionRoot.active = true; this.setSelectedLandVisual(true); const mountedLandBubble = this.landBubbleNode(); if (mountedLandBubble) { mountedLandBubble.active = true; } } private onRootTouchEnd(event: EventTouch) { if (!this.interactionRoot?.active) { return; } if (this.resolveGridNameFromTouch(event, true)) { return; } if (this.isTouchInsideInteraction(event)) { return; } this.hideInteraction(); } private hideInteraction() { if (!this.interactionRoot) { return; } this.interactionRoot.active = false; this.setSelectedLandVisual(false); const mountedLandBubble = this.landBubbleNode(); if (mountedLandBubble) { mountedLandBubble.active = false; } } private applySourceTargetPositions(targetPosition: Vec3) { if (!this.interactionRoot) { return; } const activeSeedCount = this.activeSeedCount(); const followNode = this.findByPath(this.interactionRoot, 'followNode'); const landTarget = this.findByPath(this.interactionRoot, 'landTarget'); const guideNode = this.findByPath(this.interactionRoot, 'guide'); const followPosition = targetPosition.clone(); if (activeSeedCount > 3) { followPosition.x = 0; } if (followNode) { followNode.setPosition(followPosition); } if (landTarget) { landTarget.setPosition(targetPosition); } if (guideNode) { guideNode.setPosition(targetPosition); } } private activeSeedCount(): number { if (!this.interactionRoot) { return STAGE1_SEED_MOCK.length; } const group = this.findByPath(this.interactionRoot, 'followNode/seedGroup/group_5'); if (!group) { return STAGE1_SEED_MOCK.length; } return group.children.filter((child) => child.active && /^node\d+$/.test(child.name)).length; } private resolveGridNameFromTouch(event: EventTouch, requireInsideIcon: boolean): string | null { if (!this.gridOrigin) { return null; } const location = event.getUILocation(); const halfWidth = this.sourceLandIconWidth / 2; const halfHeight = this.sourceLandIconHeight / 2; let bestGridName: string | null = null; let bestScore = Number.POSITIVE_INFINITY; for (const land of STAGE1_VISIBLE_LAND_STATE) { const grid = this.gridOrigin.getChildByName(land.gridName); const icon = grid?.getChildByName('icon'); if (!grid || !icon) { continue; } const center = icon.worldPosition; const dx = Math.abs(location.x - center.x); const dy = Math.abs(location.y - center.y); const insideIcon = dx <= halfWidth && dy <= halfHeight; if (requireInsideIcon && !insideIcon) { continue; } const score = dx / halfWidth + dy / halfHeight; if (score < bestScore) { bestScore = score; bestGridName = land.gridName; } } return bestGridName; } private gridIconPosition(grid: Node): Vec3 { const icon = grid.getChildByName('icon'); if (icon && this.mainCamera && this.uiCamera && this.interactionRoot?.parent) { const screenPosition = this.mainCamera.worldToScreen(icon.worldPosition, new Vec3()); const uiWorldPosition = this.uiCamera.screenToWorld(screenPosition, new Vec3()); const parentTransform = this.interactionRoot.parent.getComponent(UITransform); if (parentTransform) { return parentTransform.convertToNodeSpaceAR(uiWorldPosition); } return uiWorldPosition; } const iconPosition = icon ? icon.position : new Vec3(this.sourceLandIconX, this.sourceLandIconY, 0); return new Vec3(grid.position.x + iconPosition.x, grid.position.y + iconPosition.y, 0); } private setSelectedLandVisual(active: boolean) { if (!this.interactionRoot) { return; } const selected = this.findByPath(this.interactionRoot, 'landTarget/land_valid_selected'); if (selected) { selected.active = active; } } private bindSeedMock() { if (!this.interactionRoot) { return; } const group = this.findByPath(this.interactionRoot, 'followNode/seedGroup/group_5'); if (!group) { return; } for (let index = 1; index <= 5; index += 1) { const slot = group.getChildByName(`node${index}`); if (!slot) { continue; } const seed = STAGE1_SEED_MOCK[index - 1]; slot.active = !!seed; const countLabel = this.findByPath(slot, 'count')?.getComponent(Label); if (seed && countLabel) { countLabel.string = String(seed.count); } const icon = this.findByPath(slot, 'icon'); const iconSprite = icon?.getComponent(Sprite); if (iconSprite) { iconSprite.enabled = !!seed; } } this.updateSeedBackgroundVariant(); this.updateSeedLayout(group); } private mountLandDetailBubble() { if (!this.interactionRoot) { return; } const detailNode = this.findByPath(this.interactionRoot, 'followNode/detailNode'); const landBubble = this.findByPath(this.interactionRoot, 'land_detail/land'); if (detailNode && landBubble && landBubble.parent !== detailNode) { landBubble.setParent(detailNode, false); } } private landBubbleNode(): Node | null { if (!this.interactionRoot) { return null; } return this.findByPath(this.interactionRoot, 'followNode/detailNode/land') ?? this.findByPath(this.interactionRoot, 'land_detail/land'); } private updateSeedBackgroundVariant() { if (!this.interactionRoot) { return; } const compactBg = this.findByPath(this.interactionRoot, 'followNode/seedGroup/bg_node3'); const wideBg = this.findByPath(this.interactionRoot, 'followNode/seedGroup/bg_node5'); const group = this.findByPath(this.interactionRoot, 'followNode/seedGroup/group_5'); const activeSlotCount = this.activeSeedCount(); const useWideBackground = activeSlotCount > 3; if (compactBg) { compactBg.active = !useWideBackground; } if (wideBg) { wideBg.active = useWideBackground; } } private isTouchInsideInteraction(event: EventTouch): boolean { if (!this.interactionRoot) { return false; } for (const path of ['followNode/detailNode/land', 'land_detail/land', 'followNode/seedGroup', 'landTarget/land_valid_selected']) { const node = this.findByPath(this.interactionRoot, path); const transform = node?.getComponent(UITransform); if (node?.activeInHierarchy && transform?.hitTest(event.getUILocation())) { return true; } } return false; } private updateSeedLayout(group: Node) { const layout = group.getComponent(Layout); if (layout) { layout.updateLayout(true); return; } const activeSlots = group.children.filter((child) => child.active && /^node\\d+$/.test(child.name)); const totalWidth = activeSlots.length * this.seedSlotWidth + Math.max(activeSlots.length - 1, 0) * this.seedLayoutSpacingX + this.seedLayoutPaddingRight; const firstX = -totalWidth / 2 + this.seedSlotWidth / 2; activeSlots.forEach((slot, index) => { slot.setPosition(firstX + index * (this.seedSlotWidth + this.seedLayoutSpacingX), slot.position.y, slot.position.z); }); } private findDescendant(root: Node, name: string): Node | null { if (root.name === name) { return root; } for (const child of root.children) { const match = this.findDescendant(child, name); if (match) { return match; } } return null; } private findByPath(root: Node, path: string): Node | null { return path.split('/').reduce((current, name) => { return current ? current.getChildByName(name) : null; }, root); } } """ interaction_script = ( interaction_script .replace("__SOURCE_LAND_ICON_X__", f"{source_land_icon_x:.3f}") .replace("__SOURCE_LAND_ICON_Y__", f"{source_land_icon_y:.3f}") .replace("__SOURCE_LAND_ICON_WIDTH__", f"{source_land_icon_width:.3f}") .replace("__SOURCE_LAND_ICON_HEIGHT__", f"{source_land_icon_height:.3f}") .replace("__SEED_SLOT_WIDTH__", f"{seed_slot_width:.3f}") .replace("__SEED_LAYOUT_SPACING_X__", f"{seed_layout_spacing_x:.3f}") .replace("__SEED_LAYOUT_PADDING_RIGHT__", f"{seed_layout_padding_right:.3f}") ) land_level_rules = [ { "landLevel": item["landLevel"], "levelName": item["levelName"], "landRes": item["landRes"], "landDryRes": item["landDryRes"], } for item in land_evidence["levelConfig"]["landLevelAssetRules"] ] visible_land_state = [ { "landId": item["landId"], "gridName": item["gridName"], "assetId": item["mockRender"]["assetId"], "landLevel": item["mockRender"]["landLevel"], "state": item["mockRender"]["state"], } for item in land_evidence["visibleLands"] ] land_mock_script = f"""import type {{ SourceLandAssetRule, SourceVisibleLand }} from '../core/SourceVisibleLand'; export const STAGE1_LAND_LEVEL_ASSET_RULES: SourceLandAssetRule[] = {json.dumps(land_level_rules, ensure_ascii=False, indent=2)}; export const STAGE1_VISIBLE_LAND_STATE: SourceVisibleLand[] = {json.dumps(visible_land_state, ensure_ascii=False, indent=2)}; """ seed_mock_script = f"""import type {{ SourceSeedMock }} from '../core/SourceLandInteraction'; export const STAGE1_SEED_MOCK: SourceSeedMock[] = {json.dumps(interaction_evidence["mockSeeds"], ensure_ascii=False, indent=2)}; """ user_mock_script = f"""import type {{ Stage1UserMock }} from '../core/SourceTopHud'; export const STAGE1_USER_MOCK: Stage1UserMock = {json.dumps(top_hud_evidence["mockUser"], ensure_ascii=False, indent=2)}; """ top_hud_script = """import { _decorator, Component, Label, Node, ProgressBar } from 'cc'; import { stage1ExpProgress } from '../core/SourceTopHud'; import { STAGE1_USER_MOCK } from '../mock/Stage1UserMock'; const { ccclass } = _decorator; @ccclass('Stage1TopHud') export class Stage1TopHud extends Component { start() { this.bindMockUser(); } private bindMockUser() { this.setLabel('HeadInfo/SubHeadInfo/txtName', STAGE1_USER_MOCK.nickname); this.setLabel('HeadInfo/SubHeadInfo/txtLevel', ` ${STAGE1_USER_MOCK.level} `); this.setLabel('HeadInfo/SubHeadInfo/txtExp', `${STAGE1_USER_MOCK.exp}/${STAGE1_USER_MOCK.expMax}`); this.setLabel('Source/root/txtGold', STAGE1_USER_MOCK.goldDisplay); this.setLabel('Source/root/txtDiamond', String(STAGE1_USER_MOCK.diamond)); const progress = this.findByPath(this.node, 'HeadInfo/SubHeadInfo/expBar')?.getComponent(ProgressBar); if (progress) { progress.progress = stage1ExpProgress(STAGE1_USER_MOCK); } } private setLabel(path: string, value: string) { const label = this.findByPath(this.node, path)?.getComponent(Label); if (label) { label.string = value; } } private findByPath(root: Node, path: string): Node | null { return path.split('/').reduce((current, name) => { return current ? current.getChildByName(name) : null; }, root); } } """ write_text(core_dir / "SourceSpace.ts", source_space) write_text(core_dir / "SourceVisibleLand.ts", source_visible_land) write_text(core_dir / "SourceLandInteraction.ts", source_land_interaction) write_text(core_dir / "SourceTopHud.ts", source_top_hud) write_text(stage_dir / "Stage1WorldCamera.ts", stage_script) write_text(stage_dir / "Stage1UiCameraViewport.ts", ui_camera_viewport_script) write_text(stage_dir / "Stage1LandInteraction.ts", interaction_script) write_text(stage_dir / "Stage1TopHud.ts", top_hud_script) write_text(mock_dir / "Stage1LandMock.ts", land_mock_script) write_text(mock_dir / "Stage1SeedMock.ts", seed_mock_script) write_text(mock_dir / "Stage1UserMock.ts", user_mock_script) for path in [ core_dir / "SourceSpace.ts", core_dir / "SourceVisibleLand.ts", core_dir / "SourceLandInteraction.ts", core_dir / "SourceTopHud.ts", stage_dir / "Stage1WorldCamera.ts", stage_dir / "Stage1UiCameraViewport.ts", stage_dir / "Stage1LandInteraction.ts", stage_dir / "Stage1TopHud.ts", mock_dir / "Stage1LandMock.ts", mock_dir / "Stage1SeedMock.ts", mock_dir / "Stage1UserMock.ts", ]: meta = { "ver": "1.0.0", "importer": "*", "imported": True, "uuid": stable_uuid(f"script:{path.relative_to(PROJECT)}"), "files": ["", ".json"], "subMetas": {}, "userData": {}, } write_json(path.with_suffix(path.suffix + ".meta"), meta) def write_config_assets(evidence: dict, land_evidence: dict, interaction_evidence: dict, decor_evidence: dict, top_hud_evidence: dict) -> None: config_dir = ASSETS / "configs" ensure_directory_meta(config_dir) stage_config = copy.deepcopy(evidence) stage_config["conversionRulesPath"] = str(RULES_PATH) manifest = { "module": "stage1_world_camera_visible_lands", "sourceEvidence": "source_export/stage1_world_camera_source.json", "landSourceEvidence": "source_export/stage1_visible_lands_source.json", "interactionSourceEvidence": "source_export/stage1_land_interaction_source.json", "decorSourceEvidence": "source_export/stage1_scene_decor_source.json", "topHudSourceEvidence": "source_export/stage1_top_hud_source.json", "conversionRules": "source_export/cocos_creator38_conversion_rules.md", "creatorScene": "assets/scenes/stage1_world_camera.scene", "designResolution": evidence["canvas"], "camera": evidence["camera"], "assets": evidence["assets"], "landAssets": land_evidence["assets"], "interactionAssets": interaction_evidence["assets"], "topHudAssets": top_hud_evidence["assets"], "decorSpineAssets": decor_evidence["decorSpineAssets"], "decorNodes": decor_evidence["generatedNodes"], "mockSeeds": interaction_evidence["mockSeeds"], "mockUser": top_hud_evidence["mockUser"], "topHudNodeCount": len(top_hud_evidence["nodes"]), "visibleLandCount": len(land_evidence["visibleLands"]), "excludedStaticGrid": land_evidence["excludedStaticGrid"], "landLevelAssetRules": land_evidence["levelConfig"]["landLevelAssetRules"], "nodes": evidence["nodes"], "layerRule": { "sourceCameraVisibility": evidence["camera"]["visibility"], "sourceUiCameraVisibility": evidence["uiCamera"]["visibility"], "creatorWorldLayer": SOURCE_WORLD_LAYER, "creatorUiLayer": SOURCE_UI_LAYER, "reason": "Source world camera includes bit 30 but not UI_2D; source uiCamera visibility includes UI_2D and renders interaction prefabs.", }, } write_json(config_dir / "stage1_world_camera.source.json", stage_config) write_json(config_dir / "stage1_visible_lands.source.json", land_evidence) write_json(config_dir / "stage1_land_interaction.source.json", interaction_evidence) write_json(config_dir / "stage1_scene_decor.source.json", decor_evidence) write_json(config_dir / "stage1_top_hud.source.json", top_hud_evidence) write_json(config_dir / "stage1_world_camera.manifest.json", manifest) for name in [ "stage1_world_camera.source.json", "stage1_visible_lands.source.json", "stage1_land_interaction.source.json", "stage1_scene_decor.source.json", "stage1_top_hud.source.json", "stage1_world_camera.manifest.json", ]: path = config_dir / name meta = { "ver": "1.0.0", "importer": "json", "imported": True, "uuid": stable_uuid(f"json:{path.relative_to(PROJECT)}"), "files": [".json"], "subMetas": {}, "userData": {}, } write_json(path.with_suffix(path.suffix + ".meta"), meta) def write_project_settings(evidence: dict) -> None: settings_path = PROJECT / "settings" / "v2" / "packages" / "project.json" settings = read_json(settings_path) if settings_path.exists() else {"__version__": "1.0.6"} settings.setdefault("general", {}) settings["general"]["designResolution"] = { "width": int(evidence["canvas"]["width"]), "height": int(evidence["canvas"]["height"]), "policy": 2, } write_json(settings_path, settings) builder_profile_path = PROJECT / "profiles" / "v2" / "packages" / "builder.json" builder_profile = read_json(builder_profile_path) if builder_profile_path.exists() else {"__version__": "1.3.9"} builder_profile["useSplashScreen"] = True builder_profile["splashScreen"] = { "displayRatio": 0.756, "totalTime": 0, "logo": {"type": "none"}, "background": { "type": "color", "color": {"x": 1, "y": 1, "z": 1, "w": 1}, }, "watermarkLocation": "default", "autoFit": True, } write_json(builder_profile_path, builder_profile) def write_texture_assets(evidence: dict, land_evidence: dict, interaction_evidence: dict, top_hud_evidence: dict) -> dict[str, dict]: texture_dir = ASSETS / "textures" / "stage1" ensure_directory_meta(ASSETS / "textures") ensure_directory_meta(texture_dir) by_id: dict[str, dict] = {} scene_land_atlas = { "id": "scene_land_atlas", "sourcePath": land_evidence["sceneLandSkeletonData"]["atlasImagePath"], } for asset in [*evidence["assets"], *land_evidence["assets"], *interaction_evidence["assets"], *top_hud_evidence["assets"], scene_land_atlas]: asset_id = asset["id"] src = Path(asset["sourcePath"]) dst = texture_dir / f"{asset_id}.png" shutil.copy2(src, dst) with Image.open(dst) as image: width, height = image.size image_uuid = stable_uuid(f"image:{asset_id}") write_json( dst.with_suffix(dst.suffix + ".meta"), image_meta(asset_id, image_uuid, width, height, asset.get("spriteFrame")), ) by_id[asset_id] = { "path": str(dst), "uuid": image_uuid, "textureUuid": f"{image_uuid}@6c48a", "spriteFrameUuid": f"{image_uuid}@f9941", "pixelWidth": width, "pixelHeight": height, } return by_id def write_spine_assets(land_evidence: dict) -> dict[str, str]: scene_land = land_evidence["sceneLandSkeletonData"] uuids = scene_land_spine_uuids() ensure_directory_meta(ASSETS / "spine") ensure_directory_meta(SPINE_DIR) json_path = SPINE_DIR / "scene_land.json" atlas_path = SPINE_DIR / "scene_land.atlas" png_path = SPINE_DIR / "scene_land.png" write_text(json_path, json.dumps(scene_land["skeletonJson"], ensure_ascii=False, separators=(",", ":")) + "\n") write_text(atlas_path, scene_land["atlasText"].lstrip("\n")) shutil.copy2(scene_land["atlasImagePath"], png_path) with Image.open(png_path) as image: width, height = image.size write_json(png_path.with_suffix(png_path.suffix + ".meta"), image_meta("scene_land", uuids["imageUuid"], width, height)) write_json( atlas_path.with_suffix(atlas_path.suffix + ".meta"), { "ver": "1.0.0", "importer": "*", "imported": True, "uuid": uuids["atlasUuid"], "files": [".atlas", ".json"], "subMetas": {}, "userData": {}, }, ) write_json( json_path.with_suffix(json_path.suffix + ".meta"), { "ver": "1.2.7", "importer": "spine-data", "imported": True, "uuid": uuids["skeletonDataUuid"], "files": [".json"], "subMetas": {}, "userData": {"atlasUuid": uuids["atlasUuid"]}, }, ) return { **uuids, "jsonPath": str(json_path), "atlasPath": str(atlas_path), "pngPath": str(png_path), "textureUuid": f"{uuids['imageUuid']}@6c48a", } def write_decor_spine_assets(decor_evidence: dict) -> dict[str, dict]: ensure_directory_meta(ASSETS / "spine") ensure_directory_meta(SPINE_DIR) written: dict[str, dict] = {} for asset in decor_evidence["decorSpineAssets"]: asset_id = asset["id"] skin_cfg_id = str(asset["skinCfgId"]) uuids = decor_spine_uuids(asset_id) asset_dir = SPINE_DIR / asset_id ensure_directory_meta(asset_dir) skel_path = asset_dir / f"{skin_cfg_id}.skel" atlas_path = asset_dir / f"{skin_cfg_id}.atlas" png_path = asset_dir / asset["atlasImageName"] shutil.copy2(asset["skeletonBinaryPath"], skel_path) shutil.copy2(asset["atlasPath"], atlas_path) shutil.copy2(asset["atlasImagePath"], png_path) with Image.open(png_path) as image: width, height = image.size write_json(png_path.with_suffix(png_path.suffix + ".meta"), image_meta(asset_id, uuids["imageUuid"], width, height)) write_json( atlas_path.with_suffix(atlas_path.suffix + ".meta"), { "ver": "1.0.0", "importer": "*", "imported": True, "uuid": uuids["atlasUuid"], "files": [".atlas", ".json"], "subMetas": {}, "userData": {}, }, ) write_json( skel_path.with_suffix(skel_path.suffix + ".meta"), { "ver": "1.2.7", "importer": "spine-data", "imported": True, "uuid": uuids["skeletonDataUuid"], "files": [".skel"], "subMetas": {}, "userData": {"atlasUuid": uuids["atlasUuid"]}, }, ) written[asset_id] = { **uuids, "skeletonPath": str(skel_path), "atlasPath": str(atlas_path), "pngPath": str(png_path), "textureUuid": f"{uuids['imageUuid']}@6c48a", } return written def source_node(evidence: dict, path: str) -> dict: for item in evidence["nodes"]: if item["path"] == path: return item raise KeyError(path) def interaction_source_node(interaction_evidence: dict, path: str) -> dict: for item in interaction_evidence["nodes"]: if item["path"] == path: return item raise KeyError(path) def top_hud_source_node(top_hud_evidence: dict, path: str) -> dict: for item in top_hud_evidence["nodes"]: if item["path"] == path: return item raise KeyError(path) def top_hud_mock_label(top_hud_evidence: dict, source: dict) -> dict: label = copy.deepcopy(source["label"]) mock_user = top_hud_evidence["mockUser"] mock_field = label.get("mockField") if mock_field == "nickname": label["string"] = str(mock_user["nickname"]) elif mock_field == "level": label["string"] = f" {mock_user['level']} " elif mock_field == "experience": label["string"] = f"{mock_user['exp']}/{mock_user['expMax']}" elif mock_field == "goldDisplay": label["string"] = str(mock_user["goldDisplay"]) elif mock_field == "diamond": label["string"] = str(mock_user["diamond"]) return label def top_hud_mock_progress(top_hud_evidence: dict) -> float: mock_user = top_hud_evidence["mockUser"] exp_max = float(mock_user["expMax"]) if exp_max <= 0: return 0.0 return max(0.0, min(1.0, float(mock_user["exp"]) / exp_max)) def add_generated_node( objects: list[dict], name: str, parent_index: int, source: dict, textures: dict[str, dict], id_key: str, sprite_type: int = 0, layer: int = SOURCE_WORLD_LAYER, label_override: dict | None = None, ) -> int: node_index = len(objects) position = source["localPosition"] objects.append( node( name, parent_index, [], [], vec3(position["x"], position["y"], position["z"]), active=bool(source.get("active", True)), layer=layer, id_key=id_key, ) ) components: list[int] = [] size_source = source.get("size") if size_source: ui_index = len(objects) anchor = source.get("anchor", {"x": 0.5, "y": 0.5}) objects.append(ui_transform(node_index, size_source["x"], size_source["y"], anchor["x"], anchor["y"])) components.append(ui_index) if source.get("widget"): widget_index = len(objects) objects.append(source_widget_component(node_index, source["widget"], id_key)) components.append(widget_index) sprite_asset_id = source.get("spriteAssetId") if sprite_asset_id: sprite_index = len(objects) objects.append( sprite_component( node_index, textures[sprite_asset_id]["spriteFrameUuid"], sprite_type=sprite_type, size_mode=0, ) ) components.append(sprite_index) label_source = label_override if label_override is not None else source.get("label") if label_source: label_index = len(objects) objects.append(label_component(node_index, label_source)) components.append(label_index) objects[node_index]["_components"] = [ref(item) for item in components] return node_index def build_top_hud_nodes( objects: list[dict], ui_root_index: int, top_hud_evidence: dict, textures: dict[str, dict], ) -> int: node_indexes: dict[str, int] = {} def add(path: str, parent_index: int, sprite_type: int = 0) -> int: source = top_hud_source_node(top_hud_evidence, path) label_override = top_hud_mock_label(top_hud_evidence, source) if source.get("label") else None node_index = add_generated_node( objects, path.rsplit("/", 1)[-1], parent_index, source, textures, path, sprite_type=sprite_type, layer=SOURCE_UI_LAYER, label_override=label_override, ) if source.get("mask"): mask_index = len(objects) objects.append(mask_component(node_index, source["mask"])) objects[node_index]["_components"].append(ref(mask_index)) if source["mask"].get("fillColorUint32") is not None: graphics_index = len(objects) objects.append(graphics_component(node_index, {"fillColorUint32": source["mask"]["fillColorUint32"]})) objects[node_index]["_components"].append(ref(graphics_index)) if source.get("progressBar"): progress_index = len(objects) objects.append(progress_bar_component(node_index, source["progressBar"], top_hud_mock_progress(top_hud_evidence))) objects[node_index]["_components"].append(ref(progress_index)) node_indexes[path] = node_index return node_index main_index = add("main_ui_v2", ui_root_index) top_hud_component_index = len(objects) objects.append(stage1_top_hud_component(main_index)) objects[main_index]["_components"].append(ref(top_hud_component_index)) head_info = add("main_ui_v2/HeadInfo", main_index) source_root = add("main_ui_v2/Source", main_index) sub_head = add("main_ui_v2/HeadInfo/SubHeadInfo", head_info) btn_exit = add("main_ui_v2/HeadInfo/btnExit", head_info) head_di = add("main_ui_v2/HeadInfo/SubHeadInfo/head_di", sub_head, sprite_type=1) img_head_di1 = add("main_ui_v2/HeadInfo/SubHeadInfo/img_head_di1", sub_head) head_mask = add("main_ui_v2/HeadInfo/SubHeadInfo/headMask", sub_head) head = add("main_ui_v2/HeadInfo/SubHeadInfo/headMask/head", head_mask) node_head_frame = add("main_ui_v2/HeadInfo/SubHeadInfo/node_head_frame", sub_head) exp_bar = add("main_ui_v2/HeadInfo/SubHeadInfo/expBar", sub_head, sprite_type=1) bar = add("main_ui_v2/HeadInfo/SubHeadInfo/expBar/Bar", exp_bar) img_level = add("main_ui_v2/HeadInfo/SubHeadInfo/img_level", sub_head) txt_name = add("main_ui_v2/HeadInfo/SubHeadInfo/txtName", sub_head) txt_level = add("main_ui_v2/HeadInfo/SubHeadInfo/txtLevel", sub_head) txt_exp = add("main_ui_v2/HeadInfo/SubHeadInfo/txtExp", sub_head) source_root_child = add("main_ui_v2/Source/root", source_root) img_source_di = add("main_ui_v2/Source/root/img_source_di", source_root_child, sprite_type=1) img_gold = add("main_ui_v2/Source/root/img_gold", source_root_child) txt_gold = add("main_ui_v2/Source/root/txtGold", source_root_child) img_source_di1 = add("main_ui_v2/Source/root/img_source_di1", source_root_child, sprite_type=1) img_diamond = add("main_ui_v2/Source/root/img_diamond", source_root_child) txt_diamond = add("main_ui_v2/Source/root/txtDiamond", source_root_child) objects[main_index]["_children"] = [ref(head_info), ref(source_root)] objects[head_info]["_children"] = [ref(sub_head), ref(btn_exit)] objects[sub_head]["_children"] = [ ref(head_di), ref(img_head_di1), ref(head_mask), ref(node_head_frame), ref(exp_bar), ref(img_level), ref(txt_name), ref(txt_level), ref(txt_exp), ] objects[head_mask]["_children"] = [ref(head)] objects[exp_bar]["_children"] = [ref(bar)] objects[source_root]["_children"] = [ref(source_root_child)] objects[source_root_child]["_children"] = [ ref(img_source_di), ref(img_gold), ref(txt_gold), ref(img_source_di1), ref(img_diamond), ref(txt_diamond), ] return main_index def build_land_interaction_nodes( objects: list[dict], ui_root_index: int, interaction_evidence: dict, textures: dict[str, dict], ) -> int: root_source = interaction_source_node(interaction_evidence, "plant_interactive_v2") interaction_index = add_generated_node( objects, "plant_interactive_v2", ui_root_index, root_source, textures, "plant_interactive_v2", layer=SOURCE_UI_LAYER, ) land_detail = add_generated_node( objects, "land_detail", interaction_index, interaction_source_node(interaction_evidence, "plant_interactive_v2/land_detail"), textures, "plant_interactive_v2/land_detail", layer=SOURCE_UI_LAYER, ) land = add_generated_node( objects, "land", land_detail, interaction_source_node(interaction_evidence, "plant_interactive_v2/land_detail/land"), textures, "plant_interactive_v2/land_detail/land", sprite_type=1, layer=SOURCE_UI_LAYER, ) title = add_generated_node( objects, "title", land, interaction_source_node(interaction_evidence, "plant_interactive_v2/land_detail/land/title"), textures, "plant_interactive_v2/land_detail/land/title", layer=SOURCE_UI_LAYER, ) land_icon = add_generated_node( objects, "icon", land, interaction_source_node(interaction_evidence, "plant_interactive_v2/land_detail/land/icon"), textures, "plant_interactive_v2/land_detail/land/icon", layer=SOURCE_UI_LAYER, ) objects[land]["_children"] = [ref(title), ref(land_icon)] objects[land_detail]["_children"] = [ref(land)] follow_node = add_generated_node( objects, "followNode", interaction_index, interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode"), textures, "plant_interactive_v2/followNode", layer=SOURCE_UI_LAYER, ) detail_node = add_generated_node( objects, "detailNode", follow_node, interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode/detailNode"), textures, "plant_interactive_v2/followNode/detailNode", layer=SOURCE_UI_LAYER, ) seed_group = add_generated_node( objects, "seedGroup", follow_node, interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode/seedGroup"), textures, "plant_interactive_v2/followNode/seedGroup", layer=SOURCE_UI_LAYER, ) bg_node3 = add_generated_node( objects, "bg_node3", seed_group, interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode/seedGroup/bg_node3"), textures, "plant_interactive_v2/followNode/seedGroup/bg_node3", sprite_type=1, layer=SOURCE_UI_LAYER, ) bg_node5 = add_generated_node( objects, "bg_node5", seed_group, interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode/seedGroup/bg_node5"), textures, "plant_interactive_v2/followNode/seedGroup/bg_node5", sprite_type=1, layer=SOURCE_UI_LAYER, ) group_5 = add_generated_node( objects, "group_5", seed_group, interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode/seedGroup/group_5"), textures, "plant_interactive_v2/followNode/seedGroup/group_5", layer=SOURCE_UI_LAYER, ) group_layout_index = len(objects) objects.append(layout_component(group_5, interaction_evidence["slotLayout"]["layoutComponent"])) objects[group_5]["_components"].append(ref(group_layout_index)) slot_indices: list[int] = [] for slot_number in range(1, 6): slot_path = f"plant_interactive_v2/followNode/seedGroup/group_5/node{slot_number}" slot = add_generated_node(objects, f"node{slot_number}", group_5, interaction_source_node(interaction_evidence, slot_path), textures, slot_path, layer=SOURCE_UI_LAYER) seed_bg = add_generated_node(objects, "seed_bg", slot, interaction_source_node(interaction_evidence, f"{slot_path}/seed_bg"), textures, f"{slot_path}/seed_bg", layer=SOURCE_UI_LAYER) icon = add_generated_node(objects, "icon", slot, interaction_source_node(interaction_evidence, f"{slot_path}/icon"), textures, f"{slot_path}/icon", layer=SOURCE_UI_LAYER) bg_txt = add_generated_node( objects, "bg_txt", slot, interaction_source_node(interaction_evidence, f"{slot_path}/bg_txt"), textures, f"{slot_path}/bg_txt", sprite_type=1, layer=SOURCE_UI_LAYER, ) color_block = add_generated_node( objects, "color_block", bg_txt, interaction_source_node(interaction_evidence, f"{slot_path}/bg_txt/color_block"), textures, f"{slot_path}/bg_txt/color_block", sprite_type=1, layer=SOURCE_UI_LAYER, ) count = add_generated_node(objects, "count", slot, interaction_source_node(interaction_evidence, f"{slot_path}/count"), textures, f"{slot_path}/count", layer=SOURCE_UI_LAYER) objects[bg_txt]["_children"] = [ref(color_block)] objects[slot]["_children"] = [ref(seed_bg), ref(icon), ref(bg_txt), ref(count)] slot_indices.append(slot) objects[group_5]["_children"] = [ref(item) for item in slot_indices] objects[seed_group]["_children"] = [ref(bg_node3), ref(bg_node5), ref(group_5)] objects[follow_node]["_children"] = [ref(detail_node), ref(seed_group)] land_target = add_generated_node( objects, "landTarget", interaction_index, interaction_source_node(interaction_evidence, "plant_interactive_v2/landTarget"), textures, "plant_interactive_v2/landTarget", layer=SOURCE_UI_LAYER, ) selected_land = add_generated_node( objects, "land_valid_selected", land_target, interaction_source_node(interaction_evidence, "plant_interactive_v2/landTarget/land_valid_selected"), textures, "plant_interactive_v2/landTarget/land_valid_selected", layer=SOURCE_UI_LAYER, ) objects[land_target]["_children"] = [ref(selected_land)] objects[interaction_index]["_children"] = [ref(land_target), ref(land_detail), ref(follow_node)] return interaction_index def build_scene( evidence: dict, land_evidence: dict, interaction_evidence: dict, decor_evidence: dict, top_hud_evidence: dict, textures: dict[str, dict], decor_spines: dict[str, dict], ) -> list[dict]: root = source_node(evidence, "root") scene_mount = source_node(evidence, "root/scene") ui_root_source = evidence.get( "uiRoot", { "localPosition": {"x": 0.0, "y": 0.0, "z": 0.0}, "size": {"x": root["size"]["x"], "y": root["size"]["y"]}, "pivot": root["pivot"], "layer": SOURCE_UI_LAYER, }, ) farm_root = source_node(evidence, "root/scene/farm_scene_v3") map_root = source_node(evidence, "root/scene/farm_scene_v3/Scene") bg_layer = source_node(evidence, "root/scene/farm_scene_v3/Scene/BgLayer") bg = source_node(evidence, "root/scene/farm_scene_v3/Scene/BgLayer/defaultbg") fg_layer = source_node(evidence, "root/scene/farm_scene_v3/Scene/FgLayer") fg = source_node(evidence, "root/scene/farm_scene_v3/Scene/FgLayer/default") source_camera = evidence["camera"] root_widget_source = root.get( "widget", { "alignFlags": 45, "originalWidth": root["size"]["x"], "originalHeight": root["size"]["y"], "sourceTemplate": "cc.Widget compact root default", }, ) ui_widget_source = ui_root_source.get( "widget", { "alignFlags": 45, "originalWidth": ui_root_source["size"]["x"], "originalHeight": ui_root_source["size"]["y"], "sourceTemplate": "cc.Widget compact ui default", }, ) objects: list[dict] = [ { "__type__": "cc.SceneAsset", "_name": "stage1_world_camera", "_objFlags": 0, "_native": "", "scene": ref(1), }, { "__type__": "cc.Scene", "_name": "stage1_world_camera", "_objFlags": 0, "_parent": None, "_children": [ref(2)], "_active": True, "_components": [], "_prefab": None, "autoReleaseAssets": False, "_globals": ref(20), "_id": SCENE_UUID, }, node("root", 1, [3], [6, 7, 8], vec3(root["localPosition"]["x"], root["localPosition"]["y"], root["localPosition"]["z"]), id_key="root"), node("scene", 2, [4, 10], [9], vec3(scene_mount["localPosition"]["x"], scene_mount["localPosition"]["y"], scene_mount["localPosition"]["z"]), id_key="root/scene"), node("Camera", 3, [], [5], vec3(source_camera["sourcePosition"]["x"], source_camera["sourcePosition"]["y"], source_camera["sourcePosition"]["z"]), id_key="root/scene/Camera"), camera_component(4, source_camera), ui_transform(2, root["size"]["x"], root["size"]["y"], root["pivot"]["x"], root["pivot"]["y"]), canvas_component(2, 5), source_widget_component(2, root_widget_source, "root"), ui_transform(3, scene_mount["size"]["x"], scene_mount["size"]["y"], scene_mount["pivot"]["x"], scene_mount["pivot"]["y"]), node("farm_scene_v3", 3, [11], [], vec3(farm_root["localPosition"]["x"], farm_root["localPosition"]["y"], farm_root["localPosition"]["z"]), id_key="root/scene/farm_scene_v3"), node("Scene", 10, [12, 16], [], vec3(map_root["localPosition"]["x"], map_root["localPosition"]["y"], map_root["localPosition"]["z"]), id_key="root/scene/farm_scene_v3/Scene"), node("BgLayer", 11, [13], [], vec3(bg_layer["localPosition"]["x"], bg_layer["localPosition"]["y"], bg_layer["localPosition"]["z"]), id_key="root/scene/farm_scene_v3/Scene/BgLayer"), node("defaultbg", 12, [], [14, 15], vec3(bg["localPosition"]["x"], bg["localPosition"]["y"], bg["localPosition"]["z"]), id_key="root/scene/farm_scene_v3/Scene/BgLayer/defaultbg"), ui_transform(13, bg["size"]["x"], bg["size"]["y"], bg["pivot"]["x"], bg["pivot"]["y"]), sprite_component(13, textures["scene_bg"]["spriteFrameUuid"]), node("FgLayer", 11, [17], [], vec3(fg_layer["localPosition"]["x"], fg_layer["localPosition"]["y"], fg_layer["localPosition"]["z"]), id_key="root/scene/farm_scene_v3/Scene/FgLayer"), node("default", 16, [], [18, 19], vec3(fg["localPosition"]["x"], fg["localPosition"]["y"], fg["localPosition"]["z"]), id_key="root/scene/farm_scene_v3/Scene/FgLayer/default"), ui_transform(17, fg["size"]["x"], fg["size"]["y"], fg["pivot"]["x"], fg["pivot"]["y"]), sprite_component(17, textures["scene_fg"]["spriteFrameUuid"]), ] world_camera_component_index = len(objects) objects.append(stage1_world_camera_component(4, 5)) objects[4]["_components"].append(ref(world_camera_component_index)) if scene_mount.get("widget"): scene_widget_index = len(objects) objects.append(source_widget_component(3, scene_mount["widget"], "root/scene")) objects[3]["_components"].append(ref(scene_widget_index)) decor_nodes = {item["path"]: item for item in decor_evidence["generatedNodes"]} decor_assets = {item["id"]: item for item in decor_evidence["decorSpineAssets"]} decor_parents = decor_evidence["sourceParentNodes"] fg_child_indices_by_name: dict[str, int] = {"default": 17} farm_skin_source = decor_parents["farmSkin"] farm_skin_index = len(objects) fg_child_indices_by_name["FarmSkin"] = farm_skin_index objects.append( node( "FarmSkin", 16, [], [], vec3( farm_skin_source["localPosition"]["x"], farm_skin_source["localPosition"]["y"], farm_skin_source["localPosition"]["z"], ), id_key="root/scene/farm_scene_v3/Scene/FgLayer/FarmSkin", ) ) farm_skin_layer_indices: dict[str, int] = {} for layer_source in decor_parents["farmSkinLayers"]: layer_index = len(objects) farm_skin_layer_indices[layer_source["name"]] = layer_index objects.append( node( layer_source["name"], farm_skin_index, [], [], vec3( layer_source["localPosition"]["x"], layer_source["localPosition"]["y"], layer_source["localPosition"]["z"], ), id_key=f"root/scene/{layer_source['path']}", ) ) objects[farm_skin_index]["_children"] = [ref(farm_skin_layer_indices[item["name"]]) for item in decor_parents["farmSkinLayers"]] house_source = decor_nodes["farm_scene_v3/Scene/FgLayer/FarmSkin/Layer2/house_201001"] house_asset = decor_assets[house_source["spineAssetId"]] house_index = len(objects) house_skeleton_index = house_index + 1 layer2_index = farm_skin_layer_indices["Layer2"] objects.extend( [ node( house_source["name"], layer2_index, [], [house_skeleton_index], vec3( house_source["localPosition"]["x"], house_source["localPosition"]["y"], house_source["localPosition"]["z"], ), id_key="root/scene/farm_scene_v3/Scene/FgLayer/FarmSkin/Layer2/house_201001", ), spine_skeleton_component( house_index, decor_spines[house_source["spineAssetId"]]["skeletonDataUuid"], str(house_asset["defaultSkin"]), str(house_asset["defaultAnimation"]), ), ] ) objects[layer2_index]["_children"] = [ref(house_index)] dog_layer_source = decor_parents["dogLayer"] dog_layer_index = len(objects) fg_child_indices_by_name["DogLayer"] = dog_layer_index objects.append( node( "DogLayer", 16, [], [], vec3( dog_layer_source["localPosition"]["x"], dog_layer_source["localPosition"]["y"], dog_layer_source["localPosition"]["z"], ), id_key="root/scene/farm_scene_v3/Scene/FgLayer/DogLayer", ) ) doghouse_source = decor_nodes["farm_scene_v3/Scene/FgLayer/DogLayer/dogHouse"] doghouse_asset = decor_assets[doghouse_source["spineAssetId"]] doghouse_index = len(objects) doghouse_skeleton_index = doghouse_index + 1 objects.extend( [ node( doghouse_source["name"], dog_layer_index, [], [doghouse_skeleton_index], vec3( doghouse_source["localPosition"]["x"], doghouse_source["localPosition"]["y"], doghouse_source["localPosition"]["z"], ), id_key="root/scene/farm_scene_v3/Scene/FgLayer/DogLayer/dogHouse", ), spine_skeleton_component( doghouse_index, decor_spines[doghouse_source["spineAssetId"]]["skeletonDataUuid"], str(doghouse_asset["defaultSkin"]), str(doghouse_asset["defaultAnimation"]), ), ] ) objects[dog_layer_index]["_children"] = [ref(doghouse_index)] for layer_source in decor_parents["fgLayerChildrenOrder"]: layer_name = layer_source["name"] if layer_name in fg_child_indices_by_name: continue layer_index = len(objects) fg_child_indices_by_name[layer_name] = layer_index objects.append( node( layer_name, 16, [], [], vec3( layer_source["localPosition"]["x"], layer_source["localPosition"]["y"], layer_source["localPosition"]["z"], ), id_key=f"root/scene/{layer_source['path']}", ) ) objects[16]["_children"] = [ ref(fg_child_indices_by_name[layer_source["name"]]) for layer_source in decor_parents["fgLayerChildrenOrder"] if layer_source["name"] in fg_child_indices_by_name ] ui_index = len(objects) ui_camera_index = ui_index + 1 ui_transform_index = ui_index + 2 ui_widget_index = ui_index + 3 ui_camera_component_index = ui_index + 4 ui_camera_viewport_component_index = ui_index + 5 ui_camera_source = evidence["uiCamera"] objects[2]["_children"] = [ref(3), ref(ui_index)] objects.extend( [ node( "ui", 2, [ui_camera_index], [ui_transform_index, ui_widget_index], vec3( ui_root_source["localPosition"]["x"], ui_root_source["localPosition"]["y"], ui_root_source["localPosition"]["z"], ), layer=int(ui_root_source.get("layer", SOURCE_UI_LAYER)), id_key="root/ui", ), node( "uiCamera", ui_index, [], [ui_camera_component_index, ui_camera_viewport_component_index], vec3( ui_camera_source["sourcePosition"]["x"], ui_camera_source["sourcePosition"]["y"], ui_camera_source["sourcePosition"]["z"], ), layer=SOURCE_UI_LAYER, id_key="root/ui/uiCamera", ), ui_transform(ui_index, ui_root_source["size"]["x"], ui_root_source["size"]["y"], ui_root_source["pivot"]["x"], ui_root_source["pivot"]["y"]), source_widget_component(ui_index, ui_widget_source, "root/ui"), camera_component(ui_camera_index, ui_camera_source, component_id="component:ui-camera"), stage1_ui_camera_viewport_component(ui_camera_index), ] ) # Source startup.scene Canvas serializes `_cameraComponent=-5`; Cocos # compiled-json refs resolve that to instance 7, the `root/ui/uiCamera` # Camera component. Keep the world camera independent from Canvas resize. objects[7]["_cameraComponent"] = ref(ui_camera_component_index) pre_plant_source = interaction_evidence["farmGridGraphics"]["prePlantNode"] post_plant_source = interaction_evidence["farmGridGraphics"]["postPlantNode"] pre_plant_index = len(objects) post_plant_index = pre_plant_index + 1 objects.extend( [ node( "PrePlant", 10, [], [], vec3( pre_plant_source["localPosition"]["x"], pre_plant_source["localPosition"]["y"], pre_plant_source["localPosition"]["z"], ), id_key="root/scene/farm_scene_v3/PrePlant", ), node( "PostPlant", 10, [], [], vec3( post_plant_source["localPosition"]["x"], post_plant_source["localPosition"]["y"], post_plant_source["localPosition"]["z"], ), id_key="root/scene/farm_scene_v3/PostPlant", ), ] ) transform_nodes = land_evidence["gridRoot"]["transformNodes"] scaled_source, rotate_source, grid_origin_source = transform_nodes scaled_index = len(objects) rotate_index = scaled_index + 1 grid_origin_index = scaled_index + 2 objects[10]["_children"] = [ref(11), ref(scaled_index), ref(pre_plant_index), ref(post_plant_index)] objects.extend( [ node( "Scaled", 10, [rotate_index], [], vec3( scaled_source["localPosition"]["x"], scaled_source["localPosition"]["y"], scaled_source["localPosition"]["z"], ), id_key="root/scene/farm_scene_v3/Scaled", ), node( "Rotate", scaled_index, [grid_origin_index], [], vec3( rotate_source["localPosition"]["x"], rotate_source["localPosition"]["y"], rotate_source["localPosition"]["z"], ), id_key="root/scene/farm_scene_v3/Scaled/Rotate", ), node( "GridOrigin", rotate_index, [], [], vec3( grid_origin_source["localPosition"]["x"], grid_origin_source["localPosition"]["y"], grid_origin_source["localPosition"]["z"], ), id_key="root/scene/farm_scene_v3/Scaled/Rotate/GridOrigin", ), ] ) grid_indices: list[int] = [] icon_skeleton_sources = land_evidence["iconSkeletonComponents"] scene_land_skeleton_uuid = scene_land_spine_uuids()["skeletonDataUuid"] for source_order, land in enumerate(sorted(land_evidence["visibleLands"], key=lambda item: item["cell"]["sourceIndex"])): cell = land["cell"] icon = land["icon"] asset_id = land["mockRender"]["assetId"] skeleton_source = icon_skeleton_sources[source_order] default_animation = "land_locked" if asset_id == "land_extend" else asset_id has_expand_board = asset_id == "land_extend" grid_index = len(objects) icon_index = grid_index + 1 grid_ui_index = grid_index + 2 icon_ui_index = grid_index + 3 icon_skeleton_index = grid_index + 4 board_index = grid_index + 5 board_ui_index = grid_index + 6 board_sprite_index = grid_index + 7 grid_indices.append(grid_index) land_objects = [ node( cell["name"], grid_origin_index, [board_index, icon_index] if has_expand_board else [icon_index], [grid_ui_index], vec3(cell["localPosition"]["x"], cell["localPosition"]["y"], cell["localPosition"]["z"]), id_key=f"root/scene/{cell['path']}", ), node( "icon", grid_index, [], [icon_ui_index, icon_skeleton_index], vec3(icon["localPosition"]["x"], icon["localPosition"]["y"], icon["localPosition"]["z"]), id_key=f"root/scene/{icon['path']}", ), ui_transform(grid_index, cell["size"]["x"], cell["size"]["y"], cell["anchor"]["x"], cell["anchor"]["y"]), ui_transform(icon_index, icon["size"]["x"], icon["size"]["y"], icon["anchor"]["x"], icon["anchor"]["y"]), spine_skeleton_component( icon_index, scene_land_skeleton_uuid, str(skeleton_source["defaultSkin"]), default_animation, ), ] if has_expand_board: board_texture = textures["land_extend"] land_objects.extend( [ node( "expand_board", grid_index, [], [board_ui_index, board_sprite_index], vec3(icon["localPosition"]["x"], icon["localPosition"]["y"], icon["localPosition"]["z"]), id_key=f"root/scene/{icon['path']}/expand_board", ), ui_transform( board_index, board_texture["pixelWidth"], board_texture["pixelHeight"], 0.5, 0.5, ), sprite_component(board_index, board_texture["spriteFrameUuid"]), ] ) objects.extend(land_objects) top_hud_index = build_top_hud_nodes(objects, ui_index, top_hud_evidence, textures) interaction_index = build_land_interaction_nodes(objects, ui_index, interaction_evidence, textures) objects[grid_origin_index]["_children"] = [ref(index) for index in grid_indices] objects[ui_index]["_children"] = [ref(ui_camera_index), ref(top_hud_index), ref(interaction_index)] interaction_component_index = len(objects) objects.append(stage1_land_interaction_component(2)) objects[2]["_components"].append(ref(interaction_component_index)) template = read_json(SCENE_TEMPLATE) globals_start = len(objects) mapping = {old: globals_start + (old - 8) for old in range(8, 15)} objects.extend(remap_ids(copy.deepcopy(template[8:15]), mapping)) objects[1]["_globals"] = ref(globals_start) return objects def write_scene_assets( evidence: dict, land_evidence: dict, interaction_evidence: dict, decor_evidence: dict, top_hud_evidence: dict, textures: dict[str, dict], decor_spines: dict[str, dict], ) -> None: scene_dir = ASSETS / "scenes" ensure_directory_meta(scene_dir) scene_path = scene_dir / "stage1_world_camera.scene" write_json(scene_path, build_scene(evidence, land_evidence, interaction_evidence, decor_evidence, top_hud_evidence, textures, decor_spines)) write_json( scene_path.with_suffix(scene_path.suffix + ".meta"), { "ver": "1.1.50", "importer": "scene", "imported": True, "uuid": SCENE_UUID, "files": [".json"], "subMetas": {}, "userData": {}, }, ) def main() -> int: evidence = read_json(SOURCE_JSON) land_evidence = read_json(LAND_SOURCE_JSON) interaction_evidence = read_json(INTERACTION_SOURCE_JSON) decor_evidence = read_json(DECOR_SOURCE_JSON) top_hud_evidence = read_json(TOP_HUD_SOURCE_JSON) for path in [ASSETS, ASSETS / "textures", ASSETS / "spine", ASSETS / "scenes", ASSETS / "scripts", ASSETS / "configs"]: ensure_directory_meta(path) textures = write_texture_assets(evidence, land_evidence, interaction_evidence, top_hud_evidence) spine_asset = write_spine_assets(land_evidence) decor_spines = write_decor_spine_assets(decor_evidence) write_script_assets(land_evidence, interaction_evidence, top_hud_evidence) write_config_assets(evidence, land_evidence, interaction_evidence, decor_evidence, top_hud_evidence) write_project_settings(evidence) write_scene_assets(evidence, land_evidence, interaction_evidence, decor_evidence, top_hud_evidence, textures, decor_spines) report = { "status": "pass", "project": str(PROJECT), "sceneUuid": SCENE_UUID, "scenePath": str(ASSETS / "scenes" / "stage1_world_camera.scene"), "textures": textures, "spineAsset": spine_asset, "decorSpineAssets": decor_spines, "decorNodeCount": len(decor_evidence["generatedNodes"]), "visibleLandCount": len(land_evidence["visibleLands"]), "interactionAssetCount": len(interaction_evidence["assets"]), "topHudAssetCount": len(top_hud_evidence["assets"]), "topHudNodeCount": len(top_hud_evidence["nodes"]), "mockSeedCount": len(interaction_evidence["mockSeeds"]), "mockUser": top_hud_evidence["mockUser"], } print(json.dumps(report, ensure_ascii=False, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())