2026-01-18 13:11:38 +08:00
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2026-07-16 10:55:30 +08:00
using System.Runtime.InteropServices ;
2026-01-18 13:11:38 +08:00
using Ichni ;
using Ichni.Editor ;
using Ichni.RhythmGame ;
2026-07-16 10:55:30 +08:00
using Ichni.StartMenu ;
using I2.Loc.SimpleJSON ;
2026-01-18 13:11:38 +08:00
using TMPro ;
using UnityEngine ;
using UnityEngine.InputSystem ;
public class consoleOnMono : MonoBehaviour
{
2026-07-16 10:55:30 +08:00
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 ( ) ;
2026-01-18 13:11:38 +08:00
void Update ( )
{
if ( Keyboard . current . f7Key . wasPressedThisFrame )
{
2026-07-16 10:55:30 +08:00
if ( ! isImportingLegacyTbm )
{
StartCoroutine ( Read ( ) ) ;
}
2026-01-18 13:11:38 +08:00
}
2026-05-23 21:05:16 +08:00
2026-01-18 13:11:38 +08:00
}
2026-07-16 10:55:30 +08:00
public IEnumerator Read ( )
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
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 + + )
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
JSONNode node = elements [ i ] ;
string id = GetString ( node , "id" , string . Empty ) ;
if ( ! string . IsNullOrEmpty ( id ) & & ! legacyIdToNode . ContainsKey ( id ) )
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
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 ) ;
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 ;
2026-01-18 13:11:38 +08:00
}
2026-07-16 10:55:30 +08:00
if ( i % ElementsPerFrame = = 0 )
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
yield return null ;
2026-01-18 13:11:38 +08:00
}
}
2026-07-16 10:55:30 +08:00
}
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 ;
}
2026-01-18 13:11:38 +08:00
2026-07-16 10:55:30 +08:00
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 ;
}
2026-01-18 13:11:38 +08:00
2026-07-16 10:55:30 +08:00
Debug . Log ( $"[LegacyImport] Processing {pendingTrackRendererNodes.Count} pending TrackRenderer nodes..." ) ;
foreach ( KeyValuePair < Track , JSONNode > pair in pendingTrackRendererNodes )
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
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 ;
}
2026-01-18 13:11:38 +08:00
2026-07-16 10:55:30 +08:00
pathNodeCount + + ;
TryCreateLegacyElement ( node , ( ) = >
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
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 ;
}
2026-01-18 13:11:38 +08:00
2026-07-16 10:55:30 +08:00
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 , ( ) = >
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
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 ) ;
2026-01-18 13:11:38 +08:00
break ;
2026-07-16 10:55:30 +08:00
case "Stay_BM" :
noteCounts [ 1 ] + + ;
TryCreateLegacyElement ( node , ( ) = >
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
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 ;
2026-01-18 13:11:38 +08:00
2026-07-16 10:55:30 +08:00
case "Hold_BM" :
noteCounts [ 2 ] + + ;
TryCreateLegacyElement ( node , ( ) = >
{
GameElement parent = GetLegacyParentOrAncestor ( node , e = > e is Track ) ;
if ( parent = = null )
2026-01-24 20:56:43 +08:00
{
2026-07-16 10:55:30 +08:00
Debug . Log ( $"[LegacyImport] SKIP Hold_BM: parent is null, id={GetString(node, " id ", " ? ")}" ) ;
return null ;
2026-01-24 20:56:43 +08:00
}
2026-07-16 10:55:30 +08:00
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 + + ;
2026-01-18 13:11:38 +08:00
}
break ;
2026-07-16 10:55:30 +08:00
}
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 ;
}
2026-01-18 13:11:38 +08:00
2026-07-16 10:55:30 +08:00
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 , ( ) = >
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
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 ;
}
2026-01-18 13:11:38 +08:00
2026-07-16 10:55:30 +08:00
return Displacement . GenerateElement ( GetElementName ( node , "Legacy Displacement" ) ,
CreateGuid ( ) , new List < string > ( ) , true , parent ,
ConvertLegacyFlexibleFloat ( node [ "positionX" ] ) ,
ConvertLegacyFlexibleFloat ( node [ "positionY" ] ) ,
ConvertLegacyFlexibleFloat ( node [ "positionZ" ] ) ) ;
} , stats ) ;
break ;
2026-01-24 20:56:43 +08:00
2026-07-16 10:55:30 +08:00
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 ) ;
2026-01-18 13:11:38 +08:00
break ;
2026-07-16 10:55:30 +08:00
case "PositionSync_BM" :
positionSyncCount + + ;
TryCreateLegacyElement ( node , ( ) = >
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
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 ;
}
2026-01-18 13:11:38 +08:00
2026-07-16 10:55:30 +08:00
return PositionSync . GenerateElement ( GetElementName ( node , "Legacy Position Sync" ) ,
CreateGuid ( ) , new List < string > ( ) , true , parent , target , ConvertLegacyFlexibleBool ( node [ "enabling" ] , true ) ) ;
} , stats ) ;
break ;
2026-01-18 13:11:38 +08:00
2026-07-16 10:55:30 +08:00
case "BaseColorChange_BM" :
baseColorCount + + ;
TryCreateLegacyElement ( node , ( ) = >
{
GameElement parent = GetLegacyParentOrAncestor ( node , e = > e is IHaveColorSubmodule ) ;
if ( parent = = null )
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
Debug . LogWarning ( $"[LegacyImport] SKIP BaseColorChange_BM: color parent is null, id={GetString(node, " id ", " ? ")}" ) ;
return null ;
}
2026-01-18 13:11:38 +08:00
2026-07-16 10:55:30 +08:00
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 ;
2026-01-18 13:11:38 +08:00
2026-07-16 10:55:30 +08:00
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 ;
2026-01-18 13:11:38 +08:00
}
2026-07-16 10:55:30 +08:00
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 )
2026-01-18 13:11:38 +08:00
{
2026-07-16 10:55:30 +08:00
Debug . Log ( $"[LegacyImport] SKIP TrackGlobalColor_BM: Track parent is null, id={GetString(node, " id ", " ? ")}" ) ;
return null ;
2026-01-18 13:11:38 +08:00
}
2026-07-16 10:55:30 +08:00
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 ) ;
2026-01-18 13:11:38 +08:00
break ;
}
2026-07-16 10:55:30 +08:00
if ( i % ElementsPerFrame = = 0 )
{
yield return null ;
}
2026-01-18 13:11:38 +08:00
}
2026-07-16 10:55:30 +08:00
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 )
2026-01-24 20:56:43 +08:00
{
2026-07-16 10:55:30 +08:00
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 ;
2026-01-24 20:56:43 +08:00
}
2026-07-16 10:55:30 +08:00
Debug . Log ( $"[LegacyImport] Refresh complete: {refreshed} elements refreshed, {stats.failed} failures" ) ;
}
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 ;
2026-01-18 13:11:38 +08:00
}
}