import { create } from 'zustand'; import { NodeTemplate, NodeTemplates, EditorFormatConverter, BehaviorTreeAssetSerializer, NodeType } from '@esengine/behavior-tree'; interface BehaviorTreeNode { id: string; template: NodeTemplate; data: Record; position: { x: number; y: number }; children: string[]; } interface Connection { from: string; to: string; fromProperty?: string; toProperty?: string; connectionType: 'node' | 'property'; } interface BehaviorTreeState { nodes: BehaviorTreeNode[]; connections: Connection[]; selectedNodeIds: string[]; draggingNodeId: string | null; dragStartPositions: Map; isDraggingNode: boolean; // 黑板变量 blackboardVariables: Record; // 初始黑板变量(设计时的值,用于保存) initialBlackboardVariables: Record; // 是否正在运行行为树 isExecuting: boolean; // 画布变换 canvasOffset: { x: number; y: number }; canvasScale: number; isPanning: boolean; panStart: { x: number; y: number }; // 连接状态 connectingFrom: string | null; connectingFromProperty: string | null; connectingToPos: { x: number; y: number } | null; // 框选状态 isBoxSelecting: boolean; boxSelectStart: { x: number; y: number } | null; boxSelectEnd: { x: number; y: number } | null; // 拖动偏移 dragDelta: { dx: number; dy: number }; // 强制更新计数器 forceUpdateCounter: number; // Actions setNodes: (nodes: BehaviorTreeNode[]) => void; updateNodes: (updater: (nodes: BehaviorTreeNode[]) => BehaviorTreeNode[]) => void; addNode: (node: BehaviorTreeNode) => void; removeNodes: (nodeIds: string[]) => void; updateNodePosition: (nodeId: string, position: { x: number; y: number }) => void; updateNodesPosition: (updates: Map) => void; setConnections: (connections: Connection[]) => void; addConnection: (connection: Connection) => void; removeConnections: (filter: (conn: Connection) => boolean) => void; setSelectedNodeIds: (nodeIds: string[]) => void; toggleNodeSelection: (nodeId: string) => void; clearSelection: () => void; startDragging: (nodeId: string, startPositions: Map) => void; stopDragging: () => void; setIsDraggingNode: (isDragging: boolean) => void; // 画布变换 Actions setCanvasOffset: (offset: { x: number; y: number }) => void; setCanvasScale: (scale: number) => void; setIsPanning: (isPanning: boolean) => void; setPanStart: (panStart: { x: number; y: number }) => void; resetView: () => void; // 连接 Actions setConnectingFrom: (nodeId: string | null) => void; setConnectingFromProperty: (propertyName: string | null) => void; setConnectingToPos: (pos: { x: number; y: number } | null) => void; clearConnecting: () => void; // 框选 Actions setIsBoxSelecting: (isSelecting: boolean) => void; setBoxSelectStart: (pos: { x: number; y: number } | null) => void; setBoxSelectEnd: (pos: { x: number; y: number } | null) => void; clearBoxSelect: () => void; // 拖动偏移 Actions setDragDelta: (delta: { dx: number; dy: number }) => void; // 强制更新 triggerForceUpdate: () => void; // 黑板变量 Actions setBlackboardVariables: (variables: Record) => void; updateBlackboardVariable: (name: string, value: any) => void; setInitialBlackboardVariables: (variables: Record) => void; setIsExecuting: (isExecuting: boolean) => void; // 自动排序子节点 sortChildrenByPosition: () => void; // 数据导出/导入 exportToJSON: (metadata: { name: string; description: string }, blackboard: Record) => string; importFromJSON: (json: string) => { blackboard: Record }; // 运行时资产导出 exportToRuntimeAsset: ( metadata: { name: string; description: string }, blackboard: Record, format: 'json' | 'binary' ) => string | Uint8Array; // 重置所有状态 reset: () => void; } const ROOT_NODE_ID = 'root-node'; // 创建根节点模板 const createRootNodeTemplate = (): NodeTemplate => ({ type: NodeType.Composite, displayName: '根节点', category: '根节点', icon: 'TreePine', description: '行为树根节点', color: '#FFD700', defaultConfig: { nodeType: 'root' }, properties: [] }); // 创建初始根节点 const createInitialRootNode = (): BehaviorTreeNode => ({ id: ROOT_NODE_ID, template: createRootNodeTemplate(), data: { nodeType: 'root' }, position: { x: 400, y: 100 }, children: [] }); export const useBehaviorTreeStore = create((set, get) => ({ nodes: [createInitialRootNode()], connections: [], selectedNodeIds: [], draggingNodeId: null, dragStartPositions: new Map(), isDraggingNode: false, // 黑板变量初始值 blackboardVariables: {}, initialBlackboardVariables: {}, isExecuting: false, // 画布变换初始值 canvasOffset: { x: 0, y: 0 }, canvasScale: 1, isPanning: false, panStart: { x: 0, y: 0 }, // 连接状态初始值 connectingFrom: null, connectingFromProperty: null, connectingToPos: null, // 框选状态初始值 isBoxSelecting: false, boxSelectStart: null, boxSelectEnd: null, // 拖动偏移初始值 dragDelta: { dx: 0, dy: 0 }, // 强制更新计数器初始值 forceUpdateCounter: 0, setNodes: (nodes: BehaviorTreeNode[]) => set({ nodes }), updateNodes: (updater: (nodes: BehaviorTreeNode[]) => BehaviorTreeNode[]) => set((state: BehaviorTreeState) => ({ nodes: updater(state.nodes) })), addNode: (node: BehaviorTreeNode) => set((state: BehaviorTreeState) => ({ nodes: [...state.nodes, node] })), removeNodes: (nodeIds: string[]) => set((state: BehaviorTreeState) => { // 只删除指定的节点,不删除子节点 const nodesToDelete = new Set(nodeIds); // 过滤掉删除的节点,并清理所有节点的 children 引用 const remainingNodes = state.nodes .filter((n: BehaviorTreeNode) => !nodesToDelete.has(n.id)) .map((n: BehaviorTreeNode) => ({ ...n, children: n.children.filter((childId: string) => !nodesToDelete.has(childId)) })); return { nodes: remainingNodes }; }), updateNodePosition: (nodeId: string, position: { x: number; y: number }) => set((state: BehaviorTreeState) => ({ nodes: state.nodes.map((n: BehaviorTreeNode) => n.id === nodeId ? { ...n, position } : n ) })), updateNodesPosition: (updates: Map) => set((state: BehaviorTreeState) => ({ nodes: state.nodes.map((node: BehaviorTreeNode) => { const newPos = updates.get(node.id); return newPos ? { ...node, position: newPos } : node; }) })), setConnections: (connections: Connection[]) => set({ connections }), addConnection: (connection: Connection) => set((state: BehaviorTreeState) => ({ connections: [...state.connections, connection] })), removeConnections: (filter: (conn: Connection) => boolean) => set((state: BehaviorTreeState) => ({ connections: state.connections.filter(filter) })), setSelectedNodeIds: (nodeIds: string[]) => set({ selectedNodeIds: nodeIds }), toggleNodeSelection: (nodeId: string) => set((state: BehaviorTreeState) => ({ selectedNodeIds: state.selectedNodeIds.includes(nodeId) ? state.selectedNodeIds.filter((id: string) => id !== nodeId) : [...state.selectedNodeIds, nodeId] })), clearSelection: () => set({ selectedNodeIds: [] }), startDragging: (nodeId: string, startPositions: Map) => set({ draggingNodeId: nodeId, dragStartPositions: startPositions }), stopDragging: () => set({ draggingNodeId: null }), setIsDraggingNode: (isDragging: boolean) => set({ isDraggingNode: isDragging }), // 画布变换 Actions setCanvasOffset: (offset: { x: number; y: number }) => set({ canvasOffset: offset }), setCanvasScale: (scale: number) => set({ canvasScale: scale }), setIsPanning: (isPanning: boolean) => set({ isPanning }), setPanStart: (panStart: { x: number; y: number }) => set({ panStart }), resetView: () => set({ canvasOffset: { x: 0, y: 0 }, canvasScale: 1 }), // 连接 Actions setConnectingFrom: (nodeId: string | null) => set({ connectingFrom: nodeId }), setConnectingFromProperty: (propertyName: string | null) => set({ connectingFromProperty: propertyName }), setConnectingToPos: (pos: { x: number; y: number } | null) => set({ connectingToPos: pos }), clearConnecting: () => set({ connectingFrom: null, connectingFromProperty: null, connectingToPos: null }), // 框选 Actions setIsBoxSelecting: (isSelecting: boolean) => set({ isBoxSelecting: isSelecting }), setBoxSelectStart: (pos: { x: number; y: number } | null) => set({ boxSelectStart: pos }), setBoxSelectEnd: (pos: { x: number; y: number } | null) => set({ boxSelectEnd: pos }), clearBoxSelect: () => set({ isBoxSelecting: false, boxSelectStart: null, boxSelectEnd: null }), // 拖动偏移 Actions setDragDelta: (delta: { dx: number; dy: number }) => set({ dragDelta: delta }), // 强制更新 triggerForceUpdate: () => set((state: BehaviorTreeState) => ({ forceUpdateCounter: state.forceUpdateCounter + 1 })), // 黑板变量 Actions setBlackboardVariables: (variables: Record) => set({ blackboardVariables: variables }), updateBlackboardVariable: (name: string, value: any) => set((state: BehaviorTreeState) => ({ blackboardVariables: { ...state.blackboardVariables, [name]: value } })), setInitialBlackboardVariables: (variables: Record) => set({ initialBlackboardVariables: variables }), setIsExecuting: (isExecuting: boolean) => set({ isExecuting }), // 自动排序子节点(按X坐标从左到右) sortChildrenByPosition: () => set((state: BehaviorTreeState) => { const nodeMap = new Map(); state.nodes.forEach((node) => nodeMap.set(node.id, node)); const sortedNodes = state.nodes.map((node) => { if (node.children.length <= 1) { return node; } const sortedChildren = [...node.children].sort((a, b) => { const nodeA = nodeMap.get(a); const nodeB = nodeMap.get(b); if (!nodeA || !nodeB) return 0; return nodeA.position.x - nodeB.position.x; }); return { ...node, children: sortedChildren }; }); return { nodes: sortedNodes }; }), exportToJSON: (metadata: { name: string; description: string }, blackboard: Record) => { const state = get(); const now = new Date().toISOString(); const data = { version: '1.0.0', metadata: { name: metadata.name, description: metadata.description, createdAt: now, modifiedAt: now }, nodes: state.nodes, connections: state.connections, blackboard: blackboard, canvasState: { offset: state.canvasOffset, scale: state.canvasScale } }; return JSON.stringify(data, null, 2); }, importFromJSON: (json: string) => { const data = JSON.parse(json); const blackboard = data.blackboard || {}; // 重新关联最新模板:根据 className 从模板库查找 const loadedNodes: BehaviorTreeNode[] = (data.nodes || []).map((node: any) => { // 如果是根节点,使用根节点模板 if (node.id === ROOT_NODE_ID) { return { ...node, template: createRootNodeTemplate() }; } // 查找最新模板 const className = node.template?.className; if (className) { const allTemplates = NodeTemplates.getAllTemplates(); const latestTemplate = allTemplates.find((t) => t.className === className); if (latestTemplate) { return { ...node, template: latestTemplate // 使用最新模板 }; } } // 如果找不到,保留旧模板(兼容性) return node; }); set({ nodes: loadedNodes, connections: data.connections || [], blackboardVariables: blackboard, canvasOffset: data.canvasState?.offset || { x: 0, y: 0 }, canvasScale: data.canvasState?.scale || 1 }); return { blackboard }; }, exportToRuntimeAsset: ( metadata: { name: string; description: string }, blackboard: Record, format: 'json' | 'binary' ) => { const state = get(); // 构建编辑器格式数据 const editorFormat = { version: '1.0.0', metadata: { name: metadata.name, description: metadata.description, createdAt: new Date().toISOString(), modifiedAt: new Date().toISOString() }, nodes: state.nodes, connections: state.connections, blackboard: blackboard }; // 转换为资产格式 const asset = EditorFormatConverter.toAsset(editorFormat, metadata); // 序列化为指定格式 return BehaviorTreeAssetSerializer.serialize(asset, { format, pretty: format === 'json', validate: true }); }, reset: () => set({ nodes: [createInitialRootNode()], connections: [], selectedNodeIds: [], draggingNodeId: null, dragStartPositions: new Map(), isDraggingNode: false, blackboardVariables: {}, initialBlackboardVariables: {}, isExecuting: false, canvasOffset: { x: 0, y: 0 }, canvasScale: 1, isPanning: false, panStart: { x: 0, y: 0 }, connectingFrom: null, connectingFromProperty: null, connectingToPos: null, isBoxSelecting: false, boxSelectStart: null, boxSelectEnd: null, dragDelta: { dx: 0, dy: 0 }, forceUpdateCounter: 0 }) })); export type { BehaviorTreeNode, Connection }; export { ROOT_NODE_ID };