import type { Asset } from '../model/Asset'; import { ancestorIdsOf, type ObjectTree } from '../model/hierarchy'; import { instanceTargetOf, isEmptyEdit, resolveObjects } from '../model/instance'; import { findAsset, type Project } from '../model/Project'; import type { SceneObject } from '../model/SceneObject'; import { type PropertySchema, schemaFor, schemaForAsset, schemaForObject } from './PropertySchema'; import { deviatesFromAsset, isModified } from './resolve'; /** * The rows the asset inspector marks as modified. A behavior's variables are left out: their * default is declared on the variable itself, so the asset value cannot differ from it. */ export function markedAssetSchema(project: Project, asset: Asset): PropertySchema { // Declared variables are drawn by the inspector's own section, not as asset rows. return asset.type === 'behavior' || asset.type === 'variables' || asset.type === 'struct' ? schemaFor(asset.type) : schemaForAsset(project, asset); } /** Whether any asset property the inspector would mark gold differs from its default. */ export function assetDeviates(project: Project, asset: Asset): boolean { return markedAssetSchema(project, asset).some((def) => isModified(project, asset, def.key)); } /** * Whether a placed object deviates from whatever it follows: the container template it was * resolved from, or its asset. `holder` must be the stored holder – what an object follows is * decided by where it lives, and in a resolved view everything looks stored. */ export function objectDeviates(project: Project, holder: ObjectTree, object: SceneObject): boolean { const target = instanceTargetOf(project, holder, object.id); if (target) { // Instance edits are pruned to what really deviates, so their presence is the answer. const edit = target.instance.instanceEdits[target.path]; return edit !== undefined && !isEmptyEdit(edit); } if (object.editorOffset.x !== 0 || object.editorOffset.y !== 0) return true; const asset = findAsset(project, object.assetId); if (!asset) return false; return schemaForObject(project, asset, object).some((def) => deviatesFromAsset(project, object, asset, def.key), ); } /** * Ids the hierarchy marks: everything deviating itself, plus every ancestor of one. The ancestors * matter because subtrees fold – without them a collapsed row would hide that something below it * was changed. */ export function deviationMarks(project: Project, holder: ObjectTree): Set { const view: ObjectTree = { objects: resolveObjects(project, holder) }; const marks = new Set(); for (const object of view.objects) { if (!objectDeviates(project, holder, object)) continue; marks.add(object.id); for (const ancestorId of ancestorIdsOf(view, object.id)) marks.add(ancestorId); } return marks; }