@@ -35,27 +35,97 @@ namespace S1API.Internal.Entities
3535 internal sealed class NPCPrefabIdentity : MonoBehaviour
3636 {
3737 private static readonly Log Logger = new Log ( "NPCPrefabIdentity" ) ;
38-
39- // fields for Mono compatibility (auto-serialized there)
40- internal string Id ;
41- internal string FirstName ;
42- internal string LastName ;
43- internal Sprite Icon ;
44- internal S1AvatarFramework . AvatarSettings AppearanceDefaults ;
45- internal string DealerHomeBuildingName ;
46- private string PrefabName ;
47-
48- // Relationship data fields for Mono compatibility
49- private float ? RelationDelta ;
50- private bool ? Unlocked ;
51- private NPCRelationship . UnlockType ? UnlockType ;
52- private List < string > ConnectionIDs ;
38+
39+ // Mono prefab cloning relies on Unity serialization for these backing fields.
40+ #if IL2CPPMELON
41+ private string ? _id ;
42+ private string ? _firstName ;
43+ private string ? _lastName ;
44+ private Sprite ? _icon ;
45+ private S1AvatarFramework . AvatarSettings ? _appearanceDefaults ;
46+ private string ? _dealerHomeBuildingName ;
47+ private string ? _prefabName ;
48+ private List < string > ? _connectionIds ;
49+ #else
50+ [ SerializeField ] private string ? _id ;
51+ [ SerializeField ] private string ? _firstName ;
52+ [ SerializeField ] private string ? _lastName ;
53+ [ SerializeField ] private Sprite ? _icon ;
54+ [ SerializeField ] private S1AvatarFramework . AvatarSettings ? _appearanceDefaults ;
55+ [ SerializeField ] private string ? _dealerHomeBuildingName ;
56+ [ SerializeField ] private string ? _prefabName ;
57+ [ SerializeField ] private List < string > ? _connectionIds ;
58+ #endif
59+
60+ private float ? _relationDelta ;
61+ private bool ? _unlocked ;
62+ private NPCRelationship . UnlockType ? _unlockType ;
5363
5464 // Static registry to preserve data across network instantiation on Il2Cpp
5565 private static readonly Dictionary < string , IdentityData > _registry = new Dictionary < string , IdentityData > ( ) ;
5666 private bool _applied ;
5767 private AvatarSettingsData _cachedAppearanceDefaults ;
5868
69+ internal string ? Id
70+ {
71+ get => _id ;
72+ set => _id = value ;
73+ }
74+
75+ internal string ? FirstName
76+ {
77+ get => _firstName ;
78+ set => _firstName = value ;
79+ }
80+
81+ internal string ? LastName
82+ {
83+ get => _lastName ;
84+ set => _lastName = value ;
85+ }
86+
87+ internal Sprite ? Icon
88+ {
89+ get => _icon ;
90+ set => _icon = value ;
91+ }
92+
93+ internal S1AvatarFramework . AvatarSettings ? AppearanceDefaults
94+ {
95+ get => _appearanceDefaults ;
96+ set => _appearanceDefaults = value ;
97+ }
98+
99+ internal string ? DealerHomeBuildingName
100+ {
101+ get => _dealerHomeBuildingName ;
102+ set => _dealerHomeBuildingName = value ;
103+ }
104+
105+ internal string ? PrefabName
106+ {
107+ get => _prefabName ;
108+ set => _prefabName = value ;
109+ }
110+
111+ private float ? RelationDelta
112+ {
113+ get => _relationDelta ;
114+ set => _relationDelta = value ;
115+ }
116+
117+ private bool ? Unlocked
118+ {
119+ get => _unlocked ;
120+ set => _unlocked = value ;
121+ }
122+
123+ private NPCRelationship . UnlockType ? UnlockType
124+ {
125+ get => _unlockType ;
126+ set => _unlockType = value ;
127+ }
128+
59129 private struct IdentityData
60130 {
61131 internal string Id ;
@@ -190,7 +260,7 @@ internal void RegisterToStaticCache(string prefabName)
190260 PrefabName = normalizedName ;
191261
192262 // CRITICAL: Always check registry FIRST for connection IDs since they're set via RegisterRelationshipDataToStaticCache
193- // Component field (this.ConnectionIDs ) is never set during prefab configuration in Menu scene
263+ // Component field (_connectionIds ) is never set during prefab configuration in Menu scene
194264 // Connection IDs are only stored via RegisterRelationshipDataToStaticCache, so we must preserve them from registry
195265 List < string > connectionIDs = null ;
196266 string dealerHomeBuildingName = this . DealerHomeBuildingName ;
@@ -220,9 +290,9 @@ internal void RegisterToStaticCache(string prefabName)
220290
221291 // Only use component field if registry doesn't have connection IDs
222292 // (Component field is typically empty during prefab configuration, but check it as fallback)
223- if ( ( connectionIDs == null || connectionIDs . Count == 0 ) && this . ConnectionIDs != null && this . ConnectionIDs . Count > 0 )
293+ if ( ( connectionIDs == null || connectionIDs . Count == 0 ) && _connectionIds != null && _connectionIds . Count > 0 )
224294 {
225- connectionIDs = new List < string > ( this . ConnectionIDs ) ;
295+ connectionIDs = new List < string > ( _connectionIds ) ;
226296 }
227297
228298 var identityData = new IdentityData
@@ -340,7 +410,7 @@ private void TryRestoreFromRegistry()
340410 this . RelationDelta = dataRef . RelationDelta ;
341411 this . Unlocked = dataRef . Unlocked ;
342412 this . UnlockType = dataRef . UnlockType . HasValue ? ( NPCRelationship . UnlockType ? ) dataRef . UnlockType . Value : null ;
343- this . ConnectionIDs = dataRef . ConnectionIDs != null ? new List < string > ( dataRef . ConnectionIDs ) : null ;
413+ _connectionIds = dataRef . ConnectionIDs != null ? new List < string > ( dataRef . ConnectionIDs ) : null ;
344414 PrefabName = dataRef . PrefabName ?? PrefabName ;
345415
346416 // Debug log for connection restoration
@@ -383,7 +453,7 @@ private IEnumerator DelayedApply()
383453 private void EnsureRelationshipDataFromRegistry ( )
384454 {
385455 // Always try to restore to ensure fields are populated (Il2Cpp wipes component fields).
386- if ( ConnectionIDs == null || ConnectionIDs . Count == 0 || ! Unlocked . HasValue || ! RelationDelta . HasValue || ! UnlockType . HasValue )
456+ if ( _connectionIds == null || _connectionIds . Count == 0 || ! Unlocked . HasValue || ! RelationDelta . HasValue || ! UnlockType . HasValue )
387457 {
388458 TryRestoreFromRegistry ( ) ;
389459 }
@@ -489,9 +559,9 @@ internal void ApplyRelationshipDataTo(S1NPCs.NPC npc, bool preserveUnlockState =
489559 if ( UnlockType . HasValue )
490560 builder . SetUnlockType ( UnlockType . Value ) ;
491561
492- if ( ConnectionIDs != null && ConnectionIDs . Count > 0 )
562+ if ( _connectionIds != null && _connectionIds . Count > 0 )
493563 {
494- builder . WithConnectionsById ( ConnectionIDs ) ;
564+ builder . WithConnectionsById ( _connectionIds ) ;
495565 }
496566
497567 builder . ApplyTo ( relationData , npc , preserveUnlockState ) ;
@@ -732,11 +802,46 @@ private void EnsureAppearanceDefaults()
732802#endif
733803 private bool TryGetRegistryData ( out IdentityData data )
734804 {
735- string prefabName = gameObject . name ;
805+ string ? prefabName = PrefabName ;
806+ if ( string . IsNullOrEmpty ( prefabName ) )
807+ prefabName = gameObject . name ;
808+
809+ if ( string . IsNullOrEmpty ( prefabName ) )
810+ {
811+ data = default ;
812+ return false ;
813+ }
814+
736815 if ( prefabName . EndsWith ( "(Clone)" ) )
737816 prefabName = prefabName . Substring ( 0 , prefabName . Length - 7 ) ;
738817
739- return _registry . TryGetValue ( prefabName , out data ) ;
818+ if ( _registry . TryGetValue ( prefabName , out data ) )
819+ return true ;
820+
821+ try
822+ {
823+ var npc = GetComponent < S1NPCs . NPC > ( ) ;
824+ if ( npc != null && ! string . IsNullOrEmpty ( npc . ID ) )
825+ {
826+ foreach ( var kvp in _registry )
827+ {
828+ var entry = kvp . Value ;
829+ if ( ! string . IsNullOrEmpty ( entry . Id ) && string . Equals ( entry . Id , npc . ID , StringComparison . OrdinalIgnoreCase ) )
830+ {
831+ data = entry ;
832+ PrefabName = entry . PrefabName ?? kvp . Key ;
833+ return true ;
834+ }
835+ }
836+ }
837+ }
838+ catch
839+ {
840+ // ignored
841+ }
842+
843+ data = default ;
844+ return false ;
740845 }
741846
742847 /// <summary>
0 commit comments