Files
ichni_Creator_Studio/Assets/Scripts/CommandSystem/GameElementStructuralCommands.cs
2026-07-10 14:05:45 +08:00

246 lines
7.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Ichni.RhythmGame;
using Ichni.RhythmGame.Beatmap;
using UnityEngine;
namespace Ichni.Editor.Commands
{
internal sealed class ElementTreeSnapshot
{
private readonly List<BaseElement_BM> elements = new List<BaseElement_BM>();
private readonly Guid rootGuid;
private readonly Guid parentGuid;
private readonly int parentChildIndex;
private ElementTreeSnapshot(GameElement root)
{
rootGuid = root.elementGuid;
parentGuid = root.parentElement?.elementGuid ?? Guid.Empty;
parentChildIndex = root.parentElement != null
? root.parentElement.childElementList.IndexOf(root)
: -1;
RegisterExistingAncestorChain(root.parentElement);
foreach (GameElement element in root.GetAllGameElementsFromThis())
{
if (element == null) continue;
element.SaveBM();
if (element.matchedBM != null)
elements.Add(element.matchedBM);
element.submoduleList.RemoveAll(submodule => submodule == null);
foreach (SubmoduleBase submodule in element.submoduleList)
{
submodule.SaveBM();
if (submodule.matchedBM != null)
elements.Add(submodule.matchedBM);
}
}
}
public static ElementTreeSnapshot Capture(GameElement root)
{
return root == null ? null : new ElementTreeSnapshot(root);
}
public GameElement Restore()
{
if (elements.Count == 0) return null;
GameElement parent = parentGuid == Guid.Empty ? null : FindExistingElement(parentGuid);
RegisterExistingAncestorChain(parent);
RepairRootAttachment();
foreach (GameElement_BM gameElementBM in elements.OfType<GameElement_BM>())
{
GameElement_BM.identifier[gameElementBM.elementGuid] = gameElementBM;
}
foreach (BaseElement_BM elementBM in elements)
{
elementBM.ExecuteBM();
}
EditorManager.instance.beatmapContainer.ExecuteLowPriorityActions();
GameElement root = GameElement_BM.GetElement(rootGuid);
if (root == null) return null;
RestoreParentOrder(root, parent);
foreach (GameElement element in root.GetAllGameElementsFromThis())
{
if (element == null) continue;
element.AfterInitialize();
element.Refresh();
}
EditorManager.instance.uiManager.hierarchy.FindTab(root);
return root;
}
private GameElement FindExistingElement(Guid elementGuid)
{
GameElement element = GameElement_BM.GetElement(elementGuid);
if (element != null) return element;
return EditorManager.instance.beatmapContainer.gameElementList
.FirstOrDefault(gameElement => gameElement != null && gameElement.elementGuid == elementGuid);
}
private void RepairRootAttachment()
{
if (parentGuid == Guid.Empty) return;
GameElement_BM rootBM = elements
.OfType<GameElement_BM>()
.FirstOrDefault(gameElementBM => gameElementBM.elementGuid == rootGuid);
if (rootBM != null)
{
rootBM.attachedElementGuid = parentGuid;
}
}
private void RegisterExistingAncestorChain(GameElement parent)
{
if (parent == null) return;
List<GameElement> ancestors = new List<GameElement>();
for (GameElement current = parent; current != null; current = current.parentElement)
{
ancestors.Add(current);
}
ancestors.Reverse();
foreach (GameElement ancestor in ancestors)
{
ancestor.SaveBM();
if (ancestor.matchedBM is not GameElement_BM ancestorBM) continue;
GameElement_BM.identifier[ancestor.elementGuid] = ancestorBM;
ancestorBM.matchedElement = ancestor;
}
}
private void RestoreParentOrder(GameElement root, GameElement parent)
{
if (parent == null || parentChildIndex < 0) return;
parent.childElementList.Remove(root);
int insertIndex = Mathf.Clamp(parentChildIndex, 0, parent.childElementList.Count);
parent.childElementList.Insert(insertIndex, root);
root.transform.SetSiblingIndex(insertIndex);
parent.Refresh();
}
}
public sealed class DeleteElementCommand : ICommand
{
private readonly ElementTreeSnapshot snapshot;
private GameElement target;
public DeleteElementCommand(GameElement target)
{
this.target = target;
snapshot = ElementTreeSnapshot.Capture(target);
}
public void Execute()
{
DeleteCurrentTarget();
}
public void Undo()
{
target = snapshot?.Restore();
}
public void Redo()
{
DeleteCurrentTarget();
}
public bool TryMerge(ICommand other) => false;
private void DeleteCurrentTarget()
{
if (target == null) return;
EditorManager.instance.operationManager.CopyPasteDeleteModule.DeleteElementRaw(target);
target = null;
}
}
public sealed class PasteElementCommand : ICommand
{
private readonly CopyPasteDeleteModule module;
private readonly GameElement source;
private readonly GameElement parent;
private ElementTreeSnapshot snapshot;
private GameElement pastedRoot;
public PasteElementCommand(CopyPasteDeleteModule module, GameElement source, GameElement parent)
{
this.module = module;
this.source = source;
this.parent = parent;
}
public void Execute()
{
pastedRoot = module.PasteElementRaw(source, parent);
snapshot ??= ElementTreeSnapshot.Capture(pastedRoot);
}
public void Undo()
{
if (pastedRoot == null) return;
module.DeleteElementRaw(pastedRoot, false);
pastedRoot = null;
}
public void Redo()
{
pastedRoot = snapshot?.Restore();
}
public bool TryMerge(ICommand other) => false;
}
public sealed class CreateElementCommand : ICommand
{
private readonly Func<GameElement> createAction;
private ElementTreeSnapshot snapshot;
public GameElement CreatedElement { get; private set; }
public CreateElementCommand(Func<GameElement> createAction)
{
this.createAction = createAction;
}
public void Execute()
{
CreatedElement = createAction?.Invoke();
snapshot ??= ElementTreeSnapshot.Capture(CreatedElement);
}
public void Undo()
{
if (CreatedElement == null) return;
EditorManager.instance.operationManager.CopyPasteDeleteModule.DeleteElementRaw(CreatedElement, false);
CreatedElement = null;
}
public void Redo()
{
CreatedElement = snapshot?.Restore();
}
public bool TryMerge(ICommand other) => false;
}
}