1387 lines
58 KiB
C#
1387 lines
58 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using Ichni;
|
|
using Ichni.Editor;
|
|
using Ichni.RhythmGame;
|
|
using Ichni.StartMenu;
|
|
using I2.Loc.SimpleJSON;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
|
|
public class consoleOnMono : MonoBehaviour
|
|
{
|
|
private const string LegacyImportFileName = "old_tbm_trams.json";
|
|
private const int ElementsPerFrame = 120;
|
|
private const float LegacyHoldNoteVisualPositionScale = 0.1f;
|
|
private const string LegacyEnvironmentThemeBundleName = "basic";
|
|
private const string LegacyEnvironmentFallbackObjectName = "EmptyObject";
|
|
|
|
private bool isImportingLegacyTbm;
|
|
private readonly Dictionary<string, GameElement> legacyIdToElement = new();
|
|
private readonly Dictionary<string, JSONNode> legacyIdToNode = new();
|
|
private readonly Dictionary<Track, JSONNode> pendingTrackPathNodes = new();
|
|
private readonly Dictionary<Track, JSONNode> pendingTrackTimeNodes = new();
|
|
private readonly Dictionary<Track, JSONNode> pendingTrackRendererNodes = new();
|
|
private readonly Dictionary<Track, Color> legacyTrackColorMultipliers = new();
|
|
|
|
void Update()
|
|
{
|
|
if (Keyboard.current.f7Key.wasPressedThisFrame)
|
|
{
|
|
if (!isImportingLegacyTbm)
|
|
{
|
|
StartCoroutine(Read());
|
|
}
|
|
}
|
|
|
|
}
|
|
public IEnumerator Read()
|
|
{
|
|
isImportingLegacyTbm = true;
|
|
legacyIdToElement.Clear();
|
|
legacyIdToNode.Clear();
|
|
pendingTrackPathNodes.Clear();
|
|
pendingTrackTimeNodes.Clear();
|
|
pendingTrackRendererNodes.Clear();
|
|
legacyTrackColorMultipliers.Clear();
|
|
|
|
string path = SelectLegacyImportFilePath();
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
LogWindow.Log("Legacy tbm import canceled.", Color.yellow);
|
|
isImportingLegacyTbm = false;
|
|
yield break;
|
|
}
|
|
|
|
if (!File.Exists(path))
|
|
{
|
|
LogWindow.Log($"Legacy tbm import file not found: {path}", Color.red);
|
|
isImportingLegacyTbm = false;
|
|
yield break;
|
|
}
|
|
|
|
LogWindow.Log($"Start legacy tbm import: {path}");
|
|
|
|
JSONNode root;
|
|
try
|
|
{
|
|
root = JSONNode.Parse(File.ReadAllText(path));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogException(e);
|
|
LogWindow.Log($"Legacy tbm parse failed: {e.Message}", Color.red);
|
|
isImportingLegacyTbm = false;
|
|
yield break;
|
|
}
|
|
|
|
JSONNode elements = root["BeatMap"]["value"]["beatMapElementList_BM"];
|
|
if (elements == null || elements.Count == 0)
|
|
{
|
|
LogWindow.Log("Legacy tbm has no BeatMap.value.beatMapElementList_BM.", Color.red);
|
|
isImportingLegacyTbm = false;
|
|
yield break;
|
|
}
|
|
|
|
for (int i = 0; i < elements.Count; i++)
|
|
{
|
|
JSONNode node = elements[i];
|
|
string id = GetString(node, "id", string.Empty);
|
|
if (!string.IsNullOrEmpty(id) && !legacyIdToNode.ContainsKey(id))
|
|
{
|
|
legacyIdToNode.Add(id, node);
|
|
}
|
|
}
|
|
|
|
LegacyImportStats stats = new();
|
|
|
|
yield return GenerateLegacyMainElements(elements, stats);
|
|
yield return GenerateLegacyTrackSubmodules(stats);
|
|
yield return GenerateLegacyPathNodes(elements, stats);
|
|
yield return GenerateLegacyTrackPoints(elements, stats);
|
|
yield return GenerateLegacyNotes(elements, stats);
|
|
yield return GenerateLegacyNoteVisualOverrides(elements, stats);
|
|
yield return GenerateLegacyAnimations(elements, stats);
|
|
yield return RefreshLegacyImport(stats);
|
|
yield return ApplyLegacyPositionSyncInitialPass();
|
|
|
|
LogWindow.Log(
|
|
$"Legacy tbm import finished. Created {stats.created}, skipped {stats.skipped}, failed {stats.failed}.",
|
|
stats.failed > 0 ? Color.yellow : Color.green);
|
|
isImportingLegacyTbm = false;
|
|
}
|
|
|
|
private static string SelectLegacyImportFilePath()
|
|
{
|
|
OpenFileName file = new();
|
|
file.structSize = Marshal.SizeOf(file);
|
|
file.filter = "Legacy TBM or JSON (*.tbm;*.json)\0*.tbm;*.json\0All Files (*.*)\0*.*\0\0";
|
|
file.file = new string(new char[4096]);
|
|
file.maxFile = file.file.Length;
|
|
file.fileTitle = new string(new char[512]);
|
|
file.maxFileTitle = file.fileTitle.Length;
|
|
file.initialDir = Application.streamingAssetsPath.Replace('/', '\\');
|
|
file.title = "Select Legacy TBM Import File";
|
|
file.defExt = "json";
|
|
file.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000008;
|
|
|
|
return LocalDialog.GetOpenFileName(file) && !string.IsNullOrEmpty(file.file)
|
|
? Path.GetFullPath(file.file)
|
|
: string.Empty;
|
|
}
|
|
|
|
private IEnumerator GenerateLegacyMainElements(JSONNode elements, LegacyImportStats stats)
|
|
{
|
|
for (int i = 0; i < elements.Count; i++)
|
|
{
|
|
JSONNode node = elements[i];
|
|
string typeName = GetLegacyTypeName(node);
|
|
|
|
switch (typeName)
|
|
{
|
|
case "ElementFolder_BM":
|
|
Debug.Log($"[LegacyImport] Processing ElementFolder_BM: {GetElementName(node, "unnamed")}");
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is ElementFolder);
|
|
return ElementFolder.GenerateElement(GetElementName(node, "Legacy Folder"),
|
|
CreateGuid(), new List<string>(), true, parent);
|
|
}, stats);
|
|
break;
|
|
|
|
case "GameCamera_BM":
|
|
Debug.Log($"[LegacyImport] Processing GameCamera_BM: {GetElementName(node, "unnamed")}");
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node) ?? CreateFallbackFolder();
|
|
if (EditorManager.instance.cameraManager.haveGameCamera)
|
|
{
|
|
GameCamera existingCamera = EditorManager.instance.cameraManager.gameCamera;
|
|
ApplyLegacyGameCamera(existingCamera, node, parent);
|
|
Debug.Log($"[LegacyImport] Map GameCamera_BM to existing camera under parent='{parent.elementName}'");
|
|
return existingCamera;
|
|
}
|
|
|
|
GameCamera camera = GameCamera.GenerateElement(GetElementName(node, "Legacy Camera"),
|
|
CreateGuid(), new List<string>(), true, parent,
|
|
GetCameraViewType(node), GetFloat(node, "perspectiveAngle", 60f), GetFloat(node, "orthographicSize", 5f));
|
|
if (camera != null)
|
|
{
|
|
ApplyLegacyGameCamera(camera, node, parent);
|
|
}
|
|
|
|
return camera;
|
|
}, stats);
|
|
break;
|
|
|
|
case "Track_BM":
|
|
Debug.Log($"[LegacyImport] Processing Track_BM: {GetElementName(node, "unnamed")}");
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is ElementFolder);
|
|
if (parent == null)
|
|
{
|
|
parent = CreateFallbackFolder();
|
|
Debug.Log("[LegacyImport] Track_BM no parent, assigned to fallback folder");
|
|
}
|
|
|
|
Track track = Track.GenerateElement(GetElementName(node, "Legacy Track"),
|
|
CreateGuid(), new List<string>(), true, parent);
|
|
ApplyLegacyDuration(track, node);
|
|
Debug.Log($"[LegacyImport] Track_BM created: {track.elementName}, duration overridden={track is IHaveTimeDurationSubmodule timed && timed.timeDurationSubmodule.isOverridingDuration}");
|
|
return track;
|
|
}, stats);
|
|
break;
|
|
|
|
case "EnvironmentObject_BM":
|
|
Debug.Log($"[LegacyImport] Processing EnvironmentObject_BM: {GetElementName(node, "unnamed")}");
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node) ?? CreateFallbackFolder();
|
|
if (!TryFindLegacyEnvironmentObject(node, out string themeBundleName, out string objectName))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
EnvironmentObject environmentObject = EnvironmentObject.GenerateElement(
|
|
GetElementName(node, "Legacy Environment Object"),
|
|
CreateGuid(), new List<string>(), true, themeBundleName, objectName, parent,
|
|
GetBool(node, "isStatic", false));
|
|
ApplyLegacySubstantialObject(environmentObject, node);
|
|
Debug.Log($"[LegacyImport] EnvironmentObject_BM created: oldObject='{GetString(node, "objectName", "?")}', newObject='{themeBundleName}/{objectName}', parent='{parent.elementName}'");
|
|
return environmentObject;
|
|
}, stats);
|
|
break;
|
|
|
|
case "TimeTrackPoint_BM":
|
|
Debug.Log($"[LegacyImport] Processing TimeTrackPoint_BM as CrossTrackPoint: {GetElementName(node, "unnamed")}");
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
ElementFolder parent = GetLegacyParentOrAncestor(node, e => e is ElementFolder) as ElementFolder;
|
|
if (parent == null)
|
|
{
|
|
parent = CreateFallbackFolder();
|
|
Debug.Log("[LegacyImport] TimeTrackPoint_BM no folder parent, assigned to fallback folder");
|
|
}
|
|
|
|
return CrossTrackPoint.GenerateElement(GetElementName(node, "New Cross Track Point"),
|
|
CreateGuid(), new List<string>(), true, parent,
|
|
ConvertLegacyTrackSwitch(node["trackSwitch"]),
|
|
ConvertLegacyTrackPercent(node["trackPercent"]));
|
|
}, stats);
|
|
break;
|
|
|
|
case "TrackPath_BM":
|
|
Debug.Log($"[LegacyImport] Queue TrackPath_BM for track: {GetString(node, "id", "?")}");
|
|
QueueTrackNode(node, pendingTrackPathNodes, stats);
|
|
break;
|
|
|
|
case "TimeTrackMovable_BM":
|
|
case "TimeTrackStatic_BM":
|
|
Debug.Log($"[LegacyImport] Queue {typeName} for track: {GetString(node, "id", "?")}");
|
|
QueueTrackNode(node, pendingTrackTimeNodes, stats);
|
|
break;
|
|
|
|
case "TrackRendererAutoOrient_BM":
|
|
case "TrackRendererPathGenerator_BM":
|
|
Debug.Log($"[LegacyImport] Queue {typeName} for track: {GetString(node, "id", "?")}");
|
|
QueueTrackNode(node, pendingTrackRendererNodes, stats);
|
|
break;
|
|
}
|
|
|
|
if (i % ElementsPerFrame == 0)
|
|
{
|
|
yield return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerator GenerateLegacyTrackSubmodules(LegacyImportStats stats)
|
|
{
|
|
int index = 0;
|
|
|
|
Debug.Log($"[LegacyImport] Processing {pendingTrackPathNodes.Count} pending TrackPath nodes...");
|
|
foreach (KeyValuePair<Track, JSONNode> pair in pendingTrackPathNodes)
|
|
{
|
|
Track track = pair.Key;
|
|
JSONNode node = pair.Value;
|
|
Debug.Log($"[LegacyImport] Replace TrackPath for Track '{track.elementName}'");
|
|
ReplaceTrackPathSubmodule(track, node);
|
|
stats.created++;
|
|
|
|
if (++index % ElementsPerFrame == 0) yield return null;
|
|
}
|
|
|
|
Debug.Log($"[LegacyImport] Processing {pendingTrackTimeNodes.Count} pending TimeTrack nodes...");
|
|
foreach (KeyValuePair<Track, JSONNode> pair in pendingTrackTimeNodes)
|
|
{
|
|
Track track = pair.Key;
|
|
JSONNode node = pair.Value;
|
|
Debug.Log($"[LegacyImport] Replace TimeTrack for Track '{track.elementName}'");
|
|
ReplaceTrackTimeSubmodule(track, node);
|
|
stats.created++;
|
|
|
|
if (++index % ElementsPerFrame == 0) yield return null;
|
|
}
|
|
|
|
Debug.Log($"[LegacyImport] Processing {pendingTrackRendererNodes.Count} pending TrackRenderer nodes...");
|
|
foreach (KeyValuePair<Track, JSONNode> pair in pendingTrackRendererNodes)
|
|
{
|
|
Track track = pair.Key;
|
|
JSONNode node = pair.Value;
|
|
Debug.Log($"[LegacyImport] Replace TrackRenderer for Track '{track.elementName}'");
|
|
ReplaceTrackRendererSubmodule(track, node);
|
|
stats.created++;
|
|
|
|
if (++index % ElementsPerFrame == 0) yield return null;
|
|
}
|
|
}
|
|
|
|
private IEnumerator GenerateLegacyPathNodes(JSONNode elements, LegacyImportStats stats)
|
|
{
|
|
int pathNodeCount = 0;
|
|
for (int i = 0; i < elements.Count; i++)
|
|
{
|
|
JSONNode node = elements[i];
|
|
if (GetLegacyTypeName(node) != "PathNode_BM")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pathNodeCount++;
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
Track track = GetLegacyParentOrAncestor(node, e => e is Track) as Track;
|
|
if (track == null)
|
|
{
|
|
Debug.Log($"[LegacyImport] SKIP PathNode_BM: parent Track is null");
|
|
return null;
|
|
}
|
|
if (track.trackPathSubmodule == null)
|
|
{
|
|
Debug.Log($"[LegacyImport] SKIP PathNode_BM: Track '{track.elementName}' has no TrackPathSubmodule");
|
|
return null;
|
|
}
|
|
|
|
int index = Mathf.Clamp(GetInt(node, "index", track.trackPathSubmodule.pathNodeList.Count),
|
|
0, track.trackPathSubmodule.pathNodeList.Count);
|
|
PathNode pathNode = PathNode.GenerateElement(GetElementName(node, "Legacy PathNode"),
|
|
CreateGuid(), new List<string>(), true, track, true, index);
|
|
|
|
Vector3 position = GetVector3(node["position"], Vector3.zero);
|
|
Vector3 normal = GetVector3(node["normal"], Vector3.up);
|
|
Color color = GetColor(node["color"], Color.white);
|
|
if (legacyTrackColorMultipliers.TryGetValue(track, out Color multiplier))
|
|
{
|
|
color = MultiplyPathNodeColor(color, multiplier);
|
|
}
|
|
float size = Mathf.Max(0.001f, GetFloat(node, "size", 1f));
|
|
|
|
pathNode.transformSubmodule.originalPosition = position;
|
|
pathNode.transformSubmodule.originalEulerAngles = NormalToEuler(normal);
|
|
pathNode.transformSubmodule.originalScale = Vector3.one * size;
|
|
pathNode.transformSubmodule.Refresh();
|
|
|
|
pathNode.colorSubmodule.originalBaseColor = color;
|
|
pathNode.colorSubmodule.Refresh();
|
|
pathNode.Refresh();
|
|
|
|
Debug.Log($"[LegacyImport] PathNode_BM created: '{pathNode.elementName}', index={index}, pos={position}, size={size}");
|
|
return pathNode;
|
|
}, stats);
|
|
|
|
if (i % ElementsPerFrame == 0)
|
|
{
|
|
yield return null;
|
|
}
|
|
}
|
|
Debug.Log($"[LegacyImport] Total PathNode_BM entries processed: {pathNodeCount}");
|
|
}
|
|
|
|
private IEnumerator GenerateLegacyTrackPoints(JSONNode elements, LegacyImportStats stats)
|
|
{
|
|
int trackPercentPointCount = 0;
|
|
|
|
for (int i = 0; i < elements.Count; i++)
|
|
{
|
|
JSONNode node = elements[i];
|
|
string typeName = GetLegacyTypeName(node);
|
|
|
|
switch (typeName)
|
|
{
|
|
case "TrackPercentPoint_BM":
|
|
trackPercentPointCount++;
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
Track parent = GetLegacyParentOrAncestor(node, e => e is Track) as Track;
|
|
if (parent == null)
|
|
{
|
|
Debug.LogWarning($"[LegacyImport] SKIP TrackPercentPoint_BM: Track parent is null, id={GetString(node, "id", "?")}");
|
|
return null;
|
|
}
|
|
|
|
TrackPercentPoint point = TrackPercentPoint.GenerateElement(GetElementName(node, "Legacy Track Percent Point"),
|
|
CreateGuid(), new List<string>(), true, parent, ConvertLegacyTrackPercentPoint(node));
|
|
point.MotionAngles = GetBool(node, "MotionAngles", point.MotionAngles);
|
|
point.Refresh();
|
|
return point;
|
|
}, stats);
|
|
break;
|
|
}
|
|
|
|
if (i % ElementsPerFrame == 0)
|
|
{
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
Debug.Log($"[LegacyImport] TrackPoints summary: TrackPercentPoint={trackPercentPointCount}");
|
|
}
|
|
|
|
private IEnumerator GenerateLegacyNotes(JSONNode elements, LegacyImportStats stats)
|
|
{
|
|
int[] noteCounts = new int[4]; // 0=Tap, 1=Stay, 2=Hold, 3=unknown skipped
|
|
|
|
for (int i = 0; i < elements.Count; i++)
|
|
{
|
|
JSONNode node = elements[i];
|
|
string typeName = GetLegacyTypeName(node);
|
|
|
|
switch (typeName)
|
|
{
|
|
case "Tap_BM":
|
|
noteCounts[0]++;
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is Track);
|
|
if (parent == null)
|
|
{
|
|
Debug.Log($"[LegacyImport] SKIP Tap_BM: parent is null, id={GetString(node, "id", "?")}");
|
|
return null;
|
|
}
|
|
float judgeTime = GetFloat(node, "exactJudgeTime", 0f);
|
|
Debug.Log($"[LegacyImport] Creating Tap_BM at t={judgeTime}, parent='{parent.elementName}'");
|
|
return Tap.GenerateElement(GetElementName(node, "Legacy Tap"),
|
|
CreateGuid(), new List<string>(), true, parent, judgeTime);
|
|
}, stats);
|
|
break;
|
|
|
|
case "Stay_BM":
|
|
noteCounts[1]++;
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is Track);
|
|
if (parent == null)
|
|
{
|
|
Debug.Log($"[LegacyImport] SKIP Stay_BM: parent is null, id={GetString(node, "id", "?")}");
|
|
return null;
|
|
}
|
|
float judgeTime = GetFloat(node, "exactJudgeTime", 0f);
|
|
Debug.Log($"[LegacyImport] Creating Stay_BM at t={judgeTime}, parent='{parent.elementName}'");
|
|
return Stay.GenerateElement(GetElementName(node, "Legacy Stay"),
|
|
CreateGuid(), new List<string>(), true, parent, judgeTime);
|
|
}, stats);
|
|
break;
|
|
|
|
case "Hold_BM":
|
|
noteCounts[2]++;
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is Track);
|
|
if (parent == null)
|
|
{
|
|
Debug.Log($"[LegacyImport] SKIP Hold_BM: parent is null, id={GetString(node, "id", "?")}");
|
|
return null;
|
|
}
|
|
float start = GetFloat(node, "exactJudgeTime", 0f);
|
|
float end = ConvertLegacyHoldEndTime(node, start);
|
|
Debug.Log($"[LegacyImport] Creating Hold_BM: start={start}, end={end}, duration={end - start}, parent='{parent.elementName}'");
|
|
return Hold.GenerateElement(GetElementName(node, "Legacy Hold"),
|
|
CreateGuid(), new List<string>(), true, parent, start, end);
|
|
}, stats);
|
|
break;
|
|
|
|
default:
|
|
if (typeName.EndsWith("_BM") && !IsKnownSkippedType(typeName))
|
|
{
|
|
noteCounts[3]++;
|
|
stats.skipped++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (i % ElementsPerFrame == 0)
|
|
{
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
Debug.Log($"[LegacyImport] Notes summary: Tap={noteCounts[0]}, Stay={noteCounts[1]}, Hold={noteCounts[2]}, unknownSkipped={noteCounts[3]}");
|
|
}
|
|
|
|
private IEnumerator GenerateLegacyNoteVisualOverrides(JSONNode elements, LegacyImportStats stats)
|
|
{
|
|
int noteVisualCount = 0;
|
|
int appliedCount = 0;
|
|
|
|
for (int i = 0; i < elements.Count; i++)
|
|
{
|
|
JSONNode node = elements[i];
|
|
string typeName = GetLegacyTypeName(node);
|
|
if (!IsLegacyNoteVisualType(typeName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
noteVisualCount++;
|
|
string legacyId = GetString(node, "id", string.Empty);
|
|
if (string.IsNullOrEmpty(legacyId) || legacyIdToElement.ContainsKey(legacyId))
|
|
{
|
|
stats.skipped++;
|
|
Debug.Log($"[LegacyImport] SKIP NoteVisual override: duplicate or no ID, type={typeName}, id={legacyId}");
|
|
continue;
|
|
}
|
|
|
|
NoteBase note = GetLegacyParentOrAncestor(node, e => e is NoteBase) as NoteBase;
|
|
if (note == null || note.noteVisual == null)
|
|
{
|
|
stats.skipped++;
|
|
Debug.Log($"[LegacyImport] SKIP NoteVisual override: Note parent or generated NoteVisual is null, type={typeName}, id={legacyId}");
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
IHaveTransformSubmodule transformHost = note.noteVisual;
|
|
TransformSubmodule transformSubmodule = transformHost.transformSubmodule;
|
|
Vector3 legacyPosition = GetVector3(node["position"], transformSubmodule.originalPosition);
|
|
if (note is Hold)
|
|
{
|
|
legacyPosition *= LegacyHoldNoteVisualPositionScale;
|
|
}
|
|
|
|
transformSubmodule.originalPosition = legacyPosition;
|
|
transformSubmodule.originalEulerAngles = GetVector3(node["eulerAngles"], transformSubmodule.originalEulerAngles);
|
|
transformSubmodule.originalScale = GetVector3(node["scale"], transformSubmodule.originalScale);
|
|
transformSubmodule.Refresh();
|
|
transformHost.UpdateTransform(false);
|
|
note.noteVisual.Refresh();
|
|
|
|
legacyIdToElement.Add(legacyId, note.noteVisual);
|
|
stats.created++;
|
|
appliedCount++;
|
|
Debug.Log($"[LegacyImport] APPLY NoteVisual override {typeName} -> Note '{note.elementName}', pos={transformSubmodule.originalPosition}, euler={transformSubmodule.originalEulerAngles}, scale={transformSubmodule.originalScale}, holdPositionScale={(note is Hold ? LegacyHoldNoteVisualPositionScale : 1f)}");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
stats.failed++;
|
|
Debug.LogError($"[LegacyImport] FAILED NoteVisual override type={typeName}, id={legacyId}: {e.Message}");
|
|
}
|
|
|
|
if (i % ElementsPerFrame == 0)
|
|
{
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
Debug.Log($"[LegacyImport] NoteVisual override summary: found={noteVisualCount}, applied={appliedCount}");
|
|
}
|
|
|
|
private IEnumerator GenerateLegacyAnimations(JSONNode elements, LegacyImportStats stats)
|
|
{
|
|
int displacementCount = 0;
|
|
int swirlCount = 0;
|
|
int trackColorCount = 0;
|
|
int baseColorCount = 0;
|
|
int emissionColorCount = 0;
|
|
int environmentBaseColorCount = 0;
|
|
int environmentEmissionColorCount = 0;
|
|
int positionSyncCount = 0;
|
|
|
|
for (int i = 0; i < elements.Count; i++)
|
|
{
|
|
JSONNode node = elements[i];
|
|
string typeName = GetLegacyTypeName(node);
|
|
|
|
switch (typeName)
|
|
{
|
|
case "Displacement_BM":
|
|
displacementCount++;
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveTransformSubmodule);
|
|
if (parent == null)
|
|
{
|
|
Debug.Log($"[LegacyImport] SKIP Displacement_BM: transform parent is null, id={GetString(node, "id", "?")}");
|
|
return null;
|
|
}
|
|
|
|
return Displacement.GenerateElement(GetElementName(node, "Legacy Displacement"),
|
|
CreateGuid(), new List<string>(), true, parent,
|
|
ConvertLegacyFlexibleFloat(node["positionX"]),
|
|
ConvertLegacyFlexibleFloat(node["positionY"]),
|
|
ConvertLegacyFlexibleFloat(node["positionZ"]));
|
|
}, stats);
|
|
break;
|
|
|
|
case "Swirl_BM":
|
|
swirlCount++;
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveTransformSubmodule);
|
|
if (parent == null)
|
|
{
|
|
Debug.Log($"[LegacyImport] SKIP Swirl_BM: transform parent is null, id={GetString(node, "id", "?")}");
|
|
return null;
|
|
}
|
|
|
|
return Swirl.GenerateElement(GetElementName(node, "Legacy Swirl"),
|
|
CreateGuid(), new List<string>(), true, parent,
|
|
ConvertLegacyFlexibleFloat(node["swirlX"]),
|
|
ConvertLegacyFlexibleFloat(node["swirlY"]),
|
|
ConvertLegacyFlexibleFloat(node["swirlZ"]));
|
|
}, stats);
|
|
break;
|
|
|
|
case "PositionSync_BM":
|
|
positionSyncCount++;
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveTransformSubmodule);
|
|
if (parent == null)
|
|
{
|
|
Debug.LogWarning($"[LegacyImport] SKIP PositionSync_BM: transform parent is null, id={GetString(node, "id", "?")}");
|
|
return null;
|
|
}
|
|
|
|
GameElement target = GetLegacyPositionSyncTarget(node);
|
|
if (target == null)
|
|
{
|
|
Debug.LogWarning($"[LegacyImport] SKIP PositionSync_BM: target '{GetString(node, "targetElementName", "?")}' not found, id={GetString(node, "id", "?")}");
|
|
return null;
|
|
}
|
|
|
|
return PositionSync.GenerateElement(GetElementName(node, "Legacy Position Sync"),
|
|
CreateGuid(), new List<string>(), true, parent, target, ConvertLegacyFlexibleBool(node["enabling"], true));
|
|
}, stats);
|
|
break;
|
|
|
|
case "BaseColorChange_BM":
|
|
baseColorCount++;
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveColorSubmodule);
|
|
if (parent == null)
|
|
{
|
|
Debug.LogWarning($"[LegacyImport] SKIP BaseColorChange_BM: color parent is null, id={GetString(node, "id", "?")}");
|
|
return null;
|
|
}
|
|
|
|
return BaseColorChange.GenerateElement(GetElementName(node, "Legacy Base Color Change"),
|
|
CreateGuid(), new List<string>(), true, parent,
|
|
ConvertLegacyFlexibleFloat(node["colorR"]),
|
|
ConvertLegacyFlexibleFloat(node["colorG"]),
|
|
ConvertLegacyFlexibleFloat(node["colorB"]),
|
|
ConvertLegacyFlexibleFloat(node["colorA"]));
|
|
}, stats);
|
|
break;
|
|
|
|
case "EmissionColorChange_BM":
|
|
emissionColorCount++;
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveColorSubmodule);
|
|
if (parent == null)
|
|
{
|
|
Debug.LogWarning($"[LegacyImport] SKIP EmissionColorChange_BM: color parent is null, id={GetString(node, "id", "?")}");
|
|
return null;
|
|
}
|
|
|
|
return EmissionColorChange.GenerateElement(GetElementName(node, "Legacy Emission Color Change"),
|
|
CreateGuid(), new List<string>(), true, parent,
|
|
ConvertLegacyFlexibleFloat(node["colorR"]),
|
|
ConvertLegacyFlexibleFloat(node["colorG"]),
|
|
ConvertLegacyFlexibleFloat(node["colorB"]),
|
|
ConvertLegacyFlexibleFloat(node["colorI"]));
|
|
}, stats);
|
|
break;
|
|
|
|
case "EnvironmentObjectBaseColor_BM":
|
|
environmentBaseColorCount++;
|
|
ApplyLegacyEnvironmentBaseColor(node, stats);
|
|
break;
|
|
|
|
case "EnvironmentObjectEmissionColor_BM":
|
|
environmentEmissionColorCount++;
|
|
ApplyLegacyEnvironmentEmissionColor(node, stats);
|
|
break;
|
|
|
|
case "TrackGlobalColor_BM":
|
|
trackColorCount++;
|
|
TryCreateLegacyElement(node, () =>
|
|
{
|
|
Track parent = GetLegacyParentOrAncestor(node, e => e is Track) as Track;
|
|
if (parent == null)
|
|
{
|
|
Debug.Log($"[LegacyImport] SKIP TrackGlobalColor_BM: Track parent is null, id={GetString(node, "id", "?")}");
|
|
return null;
|
|
}
|
|
|
|
return TrackGlobalColorChange.GenerateElement(GetElementName(node, "Legacy Track Global Color"),
|
|
CreateGuid(), new List<string>(), true, parent,
|
|
ConvertLegacyFlexibleFloat(node["colorR"]),
|
|
ConvertLegacyFlexibleFloat(node["colorG"]),
|
|
ConvertLegacyFlexibleFloat(node["colorB"]),
|
|
ConvertLegacyFlexibleFloat(node["colorA"]));
|
|
}, stats);
|
|
break;
|
|
}
|
|
|
|
if (i % ElementsPerFrame == 0)
|
|
{
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
Debug.Log($"[LegacyImport] Animations summary: Displacement={displacementCount}, Swirl={swirlCount}, PositionSync={positionSyncCount}, TrackGlobalColor={trackColorCount}, BaseColor={baseColorCount}, EmissionColor={emissionColorCount}, EnvBaseColor={environmentBaseColorCount}, EnvEmissionColor={environmentEmissionColorCount}");
|
|
}
|
|
|
|
private IEnumerator RefreshLegacyImport(LegacyImportStats stats)
|
|
{
|
|
int refreshed = 0;
|
|
foreach (GameElement element in legacyIdToElement.Values)
|
|
{
|
|
try
|
|
{
|
|
if (element is Track track)
|
|
{
|
|
track.trackPathSubmodule?.Refresh();
|
|
track.trackTimeSubmodule?.Refresh();
|
|
track.trackRendererSubmodule?.Refresh();
|
|
Debug.Log($"[LegacyImport] Refreshed Track: '{track.elementName}' (submodules: path={track.trackPathSubmodule != null}, time={track.trackTimeSubmodule != null}, renderer={track.trackRendererSubmodule != null})");
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"[LegacyImport] Refreshing element: '{element.elementName}' ({element.GetType().Name})");
|
|
}
|
|
|
|
element.Refresh();
|
|
refreshed++;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
stats.failed++;
|
|
Debug.LogWarning($"[LegacyImport] Refresh failed on '{element.elementName}': {e.Message}");
|
|
}
|
|
|
|
yield return null;
|
|
}
|
|
Debug.Log($"[LegacyImport] Refresh complete: {refreshed} elements refreshed, {stats.failed} failures");
|
|
}
|
|
|
|
private IEnumerator ApplyLegacyPositionSyncInitialPass()
|
|
{
|
|
float songTime = CoreServices.TimeProvider?.SongTime ?? 0f;
|
|
int syncCount = 0;
|
|
|
|
foreach (GameElement element in legacyIdToElement.Values)
|
|
{
|
|
if (element is IHaveTransformSubmodule transformHost)
|
|
{
|
|
transformHost.UpdateTransform(false);
|
|
}
|
|
}
|
|
|
|
foreach (GameElement element in legacyIdToElement.Values)
|
|
{
|
|
if (element is not PositionSync sync)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
sync.ScheduledUpdate(UpdatePhase.Animation, songTime);
|
|
sync.ScheduledUpdate(UpdatePhase.Effect, songTime);
|
|
syncCount++;
|
|
|
|
yield return null;
|
|
}
|
|
|
|
Debug.Log($"[LegacyImport] PositionSync initial pass complete: {syncCount} syncs applied at songTime={songTime}");
|
|
}
|
|
|
|
private void TryCreateLegacyElement(JSONNode node, Func<GameElement> create, LegacyImportStats stats)
|
|
{
|
|
string legacyId = GetString(node, "id", string.Empty);
|
|
string typeName = GetLegacyTypeName(node);
|
|
string elementName = GetElementName(node, "unnamed");
|
|
|
|
if (string.IsNullOrEmpty(legacyId) || legacyIdToElement.ContainsKey(legacyId))
|
|
{
|
|
stats.skipped++;
|
|
Debug.Log($"[LegacyImport] SKIP (duplicate or no ID) type={typeName}, id={legacyId}, name={elementName}");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
GameElement element = create.Invoke();
|
|
if (element == null)
|
|
{
|
|
stats.skipped++;
|
|
Debug.Log($"[LegacyImport] SKIP (create returned null) type={typeName}, id={legacyId}, name={elementName}");
|
|
return;
|
|
}
|
|
|
|
legacyIdToElement.Add(legacyId, element);
|
|
stats.created++;
|
|
Debug.Log($"[LegacyImport] CREATE {typeName} -> {element.elementName}, id={legacyId}");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
stats.failed++;
|
|
Debug.LogError($"[LegacyImport] FAILED type={typeName}, id={legacyId}, name={elementName}: {e.Message}");
|
|
}
|
|
}
|
|
|
|
private void QueueTrackNode(JSONNode node, Dictionary<Track, JSONNode> queue, LegacyImportStats stats)
|
|
{
|
|
string legacyId = GetString(node, "id", "?");
|
|
Track track = GetLegacyParentOrAncestor(node, e => e is Track) as Track;
|
|
if (track == null)
|
|
{
|
|
stats.skipped++;
|
|
Debug.Log($"[LegacyImport] SKIP QueueTrackNode id={legacyId}: parent Track not found");
|
|
return;
|
|
}
|
|
|
|
queue[track] = node;
|
|
Debug.Log($"[LegacyImport] Queued {GetLegacyTypeName(node)} id={legacyId} -> Track '{track.elementName}'");
|
|
}
|
|
|
|
private void ReplaceTrackPathSubmodule(Track track, JSONNode node)
|
|
{
|
|
track.trackPathSubmodule?.Delete();
|
|
track.trackPathSubmodule = new TrackPathSubmodule(track,
|
|
(Track.TrackSpaceType)GetInt(node, "trackSpaceType", (int)Track.TrackSpaceType.CatmullRom),
|
|
(Track.TrackSamplingType)GetInt(node, "trackSamplingType", (int)Track.TrackSamplingType.TimeDistributed),
|
|
GetBool(node, "isClosed", false),
|
|
GetBool(node, "isShowingPath", false));
|
|
Debug.Log($"[LegacyImport] ReplaceTrackPathSubmodule done for '{track.elementName}': spaceType={track.trackPathSubmodule.trackSpaceType}");
|
|
}
|
|
|
|
private void ReplaceTrackTimeSubmodule(Track track, JSONNode node)
|
|
{
|
|
track.trackTimeSubmodule?.Delete();
|
|
|
|
if (GetLegacyTypeName(node) == "TimeTrackStatic_BM")
|
|
{
|
|
float totalTime = Mathf.Max(0.1f, GetFloat(node, "trackTotalTime", GetFloat(node, "visibleTrackTimeLength", 1f)));
|
|
track.trackTimeSubmodule = new TrackTimeSubmoduleStatic(track, totalTime, GetCurveType(node));
|
|
Debug.Log($"[LegacyImport] ReplaceTrackTimeSubmodule(Static) for '{track.elementName}': totalTime={totalTime}");
|
|
return;
|
|
}
|
|
|
|
float start = GetFloat(node, "trackStartTime", 0f);
|
|
float end = Mathf.Max(start + 0.1f, GetFloat(node, "trackEndTime", start + 1f));
|
|
float visibleLength = Mathf.Max(0.1f, GetFloat(node, "visibleTrackTimeLength", 1f));
|
|
track.trackTimeSubmodule = new TrackTimeSubmoduleMovable(track, start, end, visibleLength, GetCurveType(node));
|
|
Debug.Log($"[LegacyImport] ReplaceTrackTimeSubmodule(Movable) for '{track.elementName}': start={start}, end={end}, visibleLength={visibleLength}");
|
|
}
|
|
|
|
private void ReplaceTrackRendererSubmodule(Track track, JSONNode node)
|
|
{
|
|
track.trackRendererSubmodule?.Delete();
|
|
|
|
bool enableEmission = GetBool(node, "emissionEnabled", false);
|
|
float emissionIntensity = GetFloat(node, "emissionIntensity", 0f);
|
|
Color emissionColor = GetColor(node["emissionColor"], Color.white);
|
|
legacyTrackColorMultipliers[track] = enableEmission
|
|
? new Color(
|
|
emissionColor.r * Mathf.Pow(2f, emissionIntensity),
|
|
emissionColor.g * Mathf.Pow(2f, emissionIntensity),
|
|
emissionColor.b * Mathf.Pow(2f, emissionIntensity),
|
|
1f)
|
|
: Color.white;
|
|
|
|
Vector2 uvScale = Vector2.one;
|
|
Vector2 uvOffset = Vector2.zero;
|
|
|
|
if (GetLegacyTypeName(node) == "TrackRendererPathGenerator_BM")
|
|
{
|
|
track.trackRendererSubmodule = new TrackRendererSubmodulePathGenerator(track, enableEmission,
|
|
emissionIntensity, true, uvScale, uvOffset);
|
|
Debug.Log($"[LegacyImport] ReplaceTrackRendererSubmodule(PathGenerator) for '{track.elementName}': emission={enableEmission}, intensity={emissionIntensity}");
|
|
}
|
|
else
|
|
{
|
|
track.trackRendererSubmodule = new TrackRendererSubmoduleAutoOrient(track, enableEmission,
|
|
emissionIntensity, true, uvScale, uvOffset);
|
|
Debug.Log($"[LegacyImport] ReplaceTrackRendererSubmodule(AutoOrient) for '{track.elementName}': emission={enableEmission}, intensity={emissionIntensity}");
|
|
}
|
|
|
|
track.trackRendererSubmodule.Refresh();
|
|
}
|
|
|
|
private bool TryFindLegacyEnvironmentObject(JSONNode node, out string themeBundleName, out string objectName)
|
|
{
|
|
themeBundleName = string.Empty;
|
|
objectName = string.Empty;
|
|
|
|
string legacyObjectName = GetString(node, "objectName", string.Empty);
|
|
ThemeBundle themeBundle = ThemeBundleManager.instance.loadedThemeBundleList
|
|
.FirstOrDefault(bundle => string.Equals(bundle.themeBundleName, LegacyEnvironmentThemeBundleName, StringComparison.OrdinalIgnoreCase));
|
|
if (themeBundle == null)
|
|
{
|
|
Debug.LogWarning($"[LegacyImport] SKIP EnvironmentObject_BM: theme bundle '{LegacyEnvironmentThemeBundleName}' is not loaded, id={GetString(node, "id", "?")}, oldObject='{legacyObjectName}'");
|
|
return false;
|
|
}
|
|
|
|
GameObject asset = themeBundle.assetList_GameObject
|
|
.FirstOrDefault(obj => obj != null && string.Equals(obj.name, legacyObjectName, StringComparison.OrdinalIgnoreCase));
|
|
if (asset == null)
|
|
{
|
|
asset = themeBundle.assetList_GameObject
|
|
.FirstOrDefault(obj => obj != null && string.Equals(obj.name, LegacyEnvironmentFallbackObjectName, StringComparison.OrdinalIgnoreCase));
|
|
if (asset == null)
|
|
{
|
|
Debug.LogWarning($"[LegacyImport] SKIP EnvironmentObject_BM: object '{legacyObjectName}' and fallback '{LegacyEnvironmentFallbackObjectName}' not found in theme bundle '{themeBundle.themeBundleName}', id={GetString(node, "id", "?")}");
|
|
return false;
|
|
}
|
|
|
|
Debug.LogWarning($"[LegacyImport] EnvironmentObject_BM object '{legacyObjectName}' not found in '{themeBundle.themeBundleName}', fallback to '{asset.name}', id={GetString(node, "id", "?")}");
|
|
}
|
|
|
|
themeBundleName = themeBundle.themeBundleName;
|
|
objectName = asset.name;
|
|
return true;
|
|
}
|
|
|
|
private void ApplyLegacySubstantialObject(SubstantialObject substantialObject, JSONNode node)
|
|
{
|
|
if (substantialObject == null) return;
|
|
|
|
if (substantialObject.transformSubmodule != null)
|
|
{
|
|
substantialObject.transformSubmodule.originalPosition = GetVector3(node["position"], substantialObject.transformSubmodule.originalPosition);
|
|
substantialObject.transformSubmodule.originalEulerAngles = GetVector3(node["eulerAngles"], substantialObject.transformSubmodule.originalEulerAngles);
|
|
substantialObject.transformSubmodule.originalScale = GetVector3(node["scale"], substantialObject.transformSubmodule.originalScale);
|
|
substantialObject.transformSubmodule.Refresh();
|
|
((IHaveTransformSubmodule)substantialObject).UpdateTransform(false);
|
|
}
|
|
|
|
ApplyLegacyDuration(substantialObject, node);
|
|
substantialObject.Refresh();
|
|
}
|
|
|
|
private void ApplyLegacyEnvironmentBaseColor(JSONNode node, LegacyImportStats stats)
|
|
{
|
|
string legacyId = GetString(node, "id", string.Empty);
|
|
if (string.IsNullOrEmpty(legacyId) || legacyIdToElement.ContainsKey(legacyId))
|
|
{
|
|
stats.skipped++;
|
|
Debug.Log($"[LegacyImport] SKIP EnvironmentObjectBaseColor_BM: duplicate or no ID, id={legacyId}");
|
|
return;
|
|
}
|
|
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveColorSubmodule);
|
|
if (parent is not IHaveColorSubmodule colorHost || colorHost.colorSubmodule == null)
|
|
{
|
|
stats.skipped++;
|
|
Debug.LogWarning($"[LegacyImport] SKIP EnvironmentObjectBaseColor_BM: color parent is null, id={legacyId}");
|
|
return;
|
|
}
|
|
|
|
ColorSubmodule colorSubmodule = colorHost.colorSubmodule;
|
|
colorSubmodule.originalBaseColor = GetColor(node["baseColor"], colorSubmodule.originalBaseColor);
|
|
colorSubmodule.Refresh();
|
|
colorHost.UpdateColor(false);
|
|
parent.Refresh();
|
|
legacyIdToElement.Add(legacyId, parent);
|
|
stats.created++;
|
|
Debug.Log($"[LegacyImport] APPLY EnvironmentObjectBaseColor_BM -> '{parent.elementName}', color={colorSubmodule.originalBaseColor}");
|
|
}
|
|
|
|
private void ApplyLegacyEnvironmentEmissionColor(JSONNode node, LegacyImportStats stats)
|
|
{
|
|
string legacyId = GetString(node, "id", string.Empty);
|
|
if (string.IsNullOrEmpty(legacyId) || legacyIdToElement.ContainsKey(legacyId))
|
|
{
|
|
stats.skipped++;
|
|
Debug.Log($"[LegacyImport] SKIP EnvironmentObjectEmissionColor_BM: duplicate or no ID, id={legacyId}");
|
|
return;
|
|
}
|
|
|
|
GameElement parent = GetLegacyParentOrAncestor(node, e => e is IHaveColorSubmodule);
|
|
if (parent is not IHaveColorSubmodule colorHost || colorHost.colorSubmodule == null)
|
|
{
|
|
stats.skipped++;
|
|
Debug.LogWarning($"[LegacyImport] SKIP EnvironmentObjectEmissionColor_BM: color parent is null, id={legacyId}");
|
|
return;
|
|
}
|
|
|
|
ColorSubmodule colorSubmodule = colorHost.colorSubmodule;
|
|
colorSubmodule.emissionEnabled = true;
|
|
colorSubmodule.originalEmissionColor = GetColor(node["emissionColor"], colorSubmodule.originalEmissionColor);
|
|
colorSubmodule.originalEmissionIntensity = GetFloat(node, "intensity", colorSubmodule.originalEmissionIntensity);
|
|
colorSubmodule.Refresh();
|
|
colorHost.UpdateColor(false);
|
|
parent.Refresh();
|
|
legacyIdToElement.Add(legacyId, parent);
|
|
stats.created++;
|
|
Debug.Log($"[LegacyImport] APPLY EnvironmentObjectEmissionColor_BM -> '{parent.elementName}', color={colorSubmodule.originalEmissionColor}, intensity={colorSubmodule.originalEmissionIntensity}");
|
|
}
|
|
|
|
private void ApplyLegacyDuration(GameElement element, JSONNode node)
|
|
{
|
|
if (element is not IHaveTimeDurationSubmodule timed) return;
|
|
|
|
float start = GetFloat(node, "enableStartTime", -32767f);
|
|
float end = GetFloat(node, "enableEndTime", 32767f);
|
|
if (Mathf.Approximately(start, 0f) && Mathf.Approximately(end, 0f))
|
|
{
|
|
return;
|
|
}
|
|
|
|
timed.timeDurationSubmodule.isOverridingDuration = true;
|
|
timed.timeDurationSubmodule.startTime = start;
|
|
timed.timeDurationSubmodule.endTime = end;
|
|
}
|
|
|
|
private GameElement GetLegacyPositionSyncTarget(JSONNode node)
|
|
{
|
|
string targetElementName = GetString(node, "targetElementName", string.Empty);
|
|
if (string.IsNullOrEmpty(targetElementName))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
GameElement result = null;
|
|
int matchCount = 0;
|
|
foreach (KeyValuePair<string, GameElement> pair in legacyIdToElement)
|
|
{
|
|
if (pair.Value is IHaveTransformSubmodule &&
|
|
string.Equals(pair.Value.elementName, targetElementName, StringComparison.Ordinal))
|
|
{
|
|
result ??= pair.Value;
|
|
matchCount++;
|
|
}
|
|
}
|
|
|
|
if (matchCount > 1)
|
|
{
|
|
Debug.LogWarning($"[LegacyImport] PositionSync target name '{targetElementName}' matched {matchCount} elements, using '{result.elementName}'");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void ApplyLegacyGameCamera(GameCamera camera, JSONNode node, GameElement parent)
|
|
{
|
|
if (camera == null) return;
|
|
|
|
ReparentLegacyElement(camera, parent);
|
|
camera.elementName = GetElementName(node, camera.elementName);
|
|
camera.cameraViewType = GetCameraViewType(node);
|
|
camera.perspectiveAngle = GetFloat(node, "perspectiveAngle", camera.perspectiveAngle);
|
|
camera.orthographicSize = GetFloat(node, "orthographicSize", camera.orthographicSize);
|
|
|
|
if (camera.cam != null)
|
|
{
|
|
camera.cam.orthographic = camera.cameraViewType == GameCamera.CameraViewType.Orthographic;
|
|
camera.cam.fieldOfView = camera.perspectiveAngle;
|
|
camera.cam.orthographicSize = camera.orthographicSize;
|
|
}
|
|
|
|
if (camera.transformSubmodule != null)
|
|
{
|
|
camera.transformSubmodule.originalPosition = GetVector3(node["position"], camera.transformSubmodule.originalPosition);
|
|
camera.transformSubmodule.originalEulerAngles = GetVector3(node["eulerAngles"], camera.transformSubmodule.originalEulerAngles);
|
|
camera.transformSubmodule.Refresh();
|
|
camera.UpdateTransform(false);
|
|
}
|
|
|
|
ApplyLegacyDuration(camera, node);
|
|
camera.Refresh();
|
|
Debug.Log($"[LegacyImport] ApplyLegacyGameCamera: parent='{parent?.elementName ?? "null"}', pos={camera.transformSubmodule?.originalPosition}, euler={camera.transformSubmodule?.originalEulerAngles}, view={camera.cameraViewType}");
|
|
}
|
|
|
|
private void ReparentLegacyElement(GameElement element, GameElement parent)
|
|
{
|
|
if (element == null) return;
|
|
|
|
element.parentElement?.childElementList.Remove(element);
|
|
if (parent != null)
|
|
{
|
|
parent.childElementList.Remove(element);
|
|
parent.childElementList.Add(element);
|
|
element.parentElement = parent;
|
|
element.transform.SetParent(parent.transform);
|
|
}
|
|
else
|
|
{
|
|
element.parentElement = null;
|
|
element.transform.SetParent(null);
|
|
}
|
|
|
|
RefreshLegacyHierarchyParent(element, parent);
|
|
}
|
|
|
|
private void RefreshLegacyHierarchyParent(GameElement element, GameElement parent)
|
|
{
|
|
if (EditorManager.instance?.uiManager?.hierarchy == null) return;
|
|
|
|
if (parent != null)
|
|
{
|
|
EnsureLegacyHierarchyTab(parent);
|
|
}
|
|
|
|
if (element.connectedTab != null)
|
|
{
|
|
element.connectedTab.parentTab?.childTabList.Remove(element.connectedTab);
|
|
element.connectedTab.SetTab(element, parent, false);
|
|
}
|
|
else if (parent == null || parent.connectedTab != null)
|
|
{
|
|
EditorManager.instance.uiManager.hierarchy.GenerateTab(element, parent, false);
|
|
}
|
|
|
|
parent?.connectedTab?.SetStatus();
|
|
}
|
|
|
|
private void EnsureLegacyHierarchyTab(GameElement element)
|
|
{
|
|
if (element == null || element.connectedTab != null) return;
|
|
|
|
EnsureLegacyHierarchyTab(element.parentElement);
|
|
if (element.parentElement == null || element.parentElement.connectedTab != null)
|
|
{
|
|
EditorManager.instance.uiManager.hierarchy.GenerateTab(element, element.parentElement, false);
|
|
}
|
|
}
|
|
|
|
private GameElement GetLegacyParentOrAncestor(JSONNode node, Func<GameElement, bool> predicate = null)
|
|
{
|
|
string parentId = GetString(node, "attachedElementId", string.Empty);
|
|
HashSet<string> visited = new();
|
|
|
|
while (!string.IsNullOrEmpty(parentId) && parentId != "BeatMapContainer" && visited.Add(parentId))
|
|
{
|
|
if (legacyIdToElement.TryGetValue(parentId, out GameElement parentElement) &&
|
|
(predicate == null || predicate(parentElement)))
|
|
{
|
|
return parentElement;
|
|
}
|
|
|
|
if (!legacyIdToNode.TryGetValue(parentId, out JSONNode parentNode))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (GetLegacyTypeName(parentNode) == "GameCamera_BM")
|
|
{
|
|
GameCamera camera = EditorManager.instance?.cameraManager?.gameCamera;
|
|
if (camera != null && (predicate == null || predicate(camera)))
|
|
{
|
|
if (!legacyIdToElement.ContainsKey(parentId))
|
|
{
|
|
legacyIdToElement.Add(parentId, camera);
|
|
Debug.Log($"[LegacyImport] Restored missing GameCamera_BM parent mapping id={parentId} -> '{camera.elementName}'");
|
|
}
|
|
|
|
return camera;
|
|
}
|
|
}
|
|
|
|
parentId = GetString(parentNode, "attachedElementId", string.Empty);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private ElementFolder CreateFallbackFolder()
|
|
{
|
|
const string fallbackId = "__legacy_import_fallback_folder";
|
|
if (legacyIdToElement.TryGetValue(fallbackId, out GameElement existing))
|
|
{
|
|
Debug.Log("[LegacyImport] Reusing existing fallback folder");
|
|
return existing as ElementFolder;
|
|
}
|
|
|
|
ElementFolder folder = ElementFolder.GenerateElement("Legacy Import", CreateGuid(), new List<string>(), true, null);
|
|
legacyIdToElement.Add(fallbackId, folder);
|
|
Debug.Log("[LegacyImport] Created fallback folder: 'Legacy Import'");
|
|
return folder;
|
|
}
|
|
|
|
private static string GetLegacyTypeName(JSONNode node)
|
|
{
|
|
string type = GetString(node, "__type", string.Empty);
|
|
int commaIndex = type.IndexOf(",", StringComparison.Ordinal);
|
|
if (commaIndex >= 0) type = type.Substring(0, commaIndex);
|
|
int dotIndex = type.LastIndexOf(".", StringComparison.Ordinal);
|
|
return dotIndex >= 0 ? type.Substring(dotIndex + 1) : type;
|
|
}
|
|
|
|
private static bool IsKnownSkippedType(string typeName)
|
|
{
|
|
return IsLegacyNoteVisualType(typeName) ||
|
|
typeName.Contains("NoteEffect") ||
|
|
typeName is "Displacement_BM" or "Swirl_BM" or "TrackGlobalColor_BM" or "TimeTrackPoint_BM" or
|
|
"EnvironmentObject_BM" or "EnvironmentObjectBaseColor_BM" or "EnvironmentObjectEmissionColor_BM" or
|
|
"BaseColorChange_BM" or "EmissionColorChange_BM" or "TrackPercentPoint_BM" or "PositionSync_BM";
|
|
}
|
|
|
|
private static bool IsLegacyNoteVisualType(string typeName)
|
|
{
|
|
return typeName.Contains("NoteVisual") ||
|
|
typeName.Contains("NoteHoldVisual");
|
|
}
|
|
|
|
private static FlexibleInt ConvertLegacyTrackSwitch(JSONNode node)
|
|
{
|
|
FlexibleInt result = new();
|
|
JSONNode list = node["animatedIntList_BM"];
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
JSONNode item = list[i];
|
|
result.Add(new AnimatedInt(GetFloat(item, "startTime", 0f), GetInt(item, "value", 0)));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static FlexibleFloat ConvertLegacyTrackPercent(JSONNode node)
|
|
{
|
|
return ConvertLegacyFlexibleFloat(node);
|
|
}
|
|
|
|
private static float ConvertLegacyHoldEndTime(JSONNode node, float start)
|
|
{
|
|
if (HasJsonKey(node, "holdEndTime"))
|
|
{
|
|
return Mathf.Max(start + 0.1f, GetFloat(node, "holdEndTime", start + 0.1f));
|
|
}
|
|
|
|
float duration = GetFloat(node, "holdTime", 0.1f);
|
|
return start + Mathf.Max(0.1f, duration);
|
|
}
|
|
|
|
private static FlexibleFloat ConvertLegacyTrackPercentPoint(JSONNode node)
|
|
{
|
|
JSONNode trackPercent = node["trackPercent"];
|
|
if (trackPercent != null && trackPercent.Count > 0)
|
|
{
|
|
return ConvertLegacyFlexibleFloat(trackPercent);
|
|
}
|
|
|
|
float percent = GetFloat(node, "percent", 0f);
|
|
return new FlexibleFloat(new List<AnimatedFloat>
|
|
{
|
|
new(0f, 0.1f, percent, percent, AnimationCurveType.Linear)
|
|
});
|
|
}
|
|
|
|
private static FlexibleFloat ConvertLegacyFlexibleFloat(JSONNode node)
|
|
{
|
|
FlexibleFloat result = new();
|
|
JSONNode list = node["animatedFloatList_BM"];
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
JSONNode item = list[i];
|
|
float startTime = GetFloat(item, "startTime", 0f);
|
|
float endTime = Mathf.Max(startTime + 0.001f, GetFloat(item, "endTime", startTime + 1f));
|
|
result.Add(new AnimatedFloat(startTime, endTime,
|
|
GetFloat(item, "startValue", 0f),
|
|
GetFloat(item, "endValue", 1f),
|
|
GetCurveType(item, "presetAnimationCurveType")));
|
|
}
|
|
|
|
result.Sort();
|
|
return result;
|
|
}
|
|
|
|
private static FlexibleBool ConvertLegacyFlexibleBool(JSONNode node, bool fallback)
|
|
{
|
|
FlexibleBool result = new();
|
|
if (node == null || node.Count == 0)
|
|
{
|
|
result.Add(new AnimatedBool(0f, fallback));
|
|
return result;
|
|
}
|
|
|
|
JSONNode list = node["animatedBoolList_BM"];
|
|
if (list == null || list.Count == 0)
|
|
{
|
|
list = node["animatedBoolList"];
|
|
}
|
|
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
JSONNode item = list[i];
|
|
result.Add(new AnimatedBool(GetFloat(item, "time", 0f), GetBool(item, "value", fallback)));
|
|
}
|
|
|
|
if (result.animations.Count == 0)
|
|
{
|
|
result.Add(new AnimatedBool(0f, fallback));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static AnimationCurveType GetCurveType(JSONNode node)
|
|
{
|
|
return GetCurveType(node["presetAnimationCurve"], "animationCurveType");
|
|
}
|
|
|
|
private static AnimationCurveType GetCurveType(JSONNode node, string key)
|
|
{
|
|
int value = GetInt(node, key, 0);
|
|
return Enum.IsDefined(typeof(AnimationCurveType), value) ? (AnimationCurveType)value : AnimationCurveType.Linear;
|
|
}
|
|
|
|
private static GameCamera.CameraViewType GetCameraViewType(JSONNode node)
|
|
{
|
|
int value = GetInt(node, "cameraViewType", (int)GameCamera.CameraViewType.Perspective);
|
|
return Enum.IsDefined(typeof(GameCamera.CameraViewType), value)
|
|
? (GameCamera.CameraViewType)value
|
|
: GameCamera.CameraViewType.Perspective;
|
|
}
|
|
|
|
private static Color MultiplyPathNodeColor(Color color, Color multiplier)
|
|
{
|
|
return new Color(color.r * multiplier.r, color.g * multiplier.g, color.b * multiplier.b, color.a);
|
|
}
|
|
|
|
private static Vector3 GetVector3(JSONNode node, Vector3 fallback)
|
|
{
|
|
if (node == null) return fallback;
|
|
return new Vector3(GetFloat(node, "x", fallback.x), GetFloat(node, "y", fallback.y), GetFloat(node, "z", fallback.z));
|
|
}
|
|
|
|
private static Color GetColor(JSONNode node, Color fallback)
|
|
{
|
|
if (node == null) return fallback;
|
|
return new Color(GetFloat(node, "r", fallback.r), GetFloat(node, "g", fallback.g),
|
|
GetFloat(node, "b", fallback.b), GetFloat(node, "a", fallback.a));
|
|
}
|
|
|
|
private static Vector3 NormalToEuler(Vector3 normal)
|
|
{
|
|
if (normal.sqrMagnitude < 0.0001f) return Vector3.zero;
|
|
return Quaternion.FromToRotation(Vector3.up, normal.normalized).eulerAngles;
|
|
}
|
|
|
|
private static string GetElementName(JSONNode node, string fallback)
|
|
{
|
|
return GetString(node, "elementName", fallback);
|
|
}
|
|
|
|
private static string GetString(JSONNode node, string key, string fallback)
|
|
{
|
|
if (node == null || node[key] is JSONLazyCreator) return fallback;
|
|
string value = node[key];
|
|
return string.IsNullOrEmpty(value) ? fallback : value;
|
|
}
|
|
|
|
private static int GetInt(JSONNode node, string key, int fallback)
|
|
{
|
|
if (node == null || node[key] is JSONLazyCreator) return fallback;
|
|
return node[key].AsInt;
|
|
}
|
|
|
|
private static float GetFloat(JSONNode node, string key, float fallback)
|
|
{
|
|
if (node == null || node[key] is JSONLazyCreator) return fallback;
|
|
return node[key].AsFloat;
|
|
}
|
|
|
|
private static bool HasJsonKey(JSONNode node, string key)
|
|
{
|
|
return node != null && node[key] is not JSONLazyCreator;
|
|
}
|
|
|
|
private static bool GetBool(JSONNode node, string key, bool fallback)
|
|
{
|
|
if (node == null || node[key] is JSONLazyCreator) return fallback;
|
|
return node[key].AsBool;
|
|
}
|
|
|
|
private static Guid CreateGuid()
|
|
{
|
|
return Guid.NewGuid();
|
|
}
|
|
|
|
private sealed class LegacyImportStats
|
|
{
|
|
public int created;
|
|
public int skipped;
|
|
public int failed;
|
|
}
|
|
}
|