11#if ( IL2CPPMELON )
22using S1ItemFramework = Il2CppScheduleOne . ItemFramework ;
33using S1Registry = Il2CppScheduleOne . Registry ;
4+ using S1StationFramework = Il2CppScheduleOne . StationFramework ;
45using S1Storage = Il2CppScheduleOne . Storage ;
56#elif ( MONOMELON || MONOBEPINEX || IL2CPPBEPINEX )
67using S1ItemFramework = ScheduleOne . ItemFramework ;
78using S1Registry = ScheduleOne . Registry ;
9+ using S1StationFramework = ScheduleOne . StationFramework ;
810using S1Storage = ScheduleOne . Storage ;
911#endif
1012
13+ using System ;
14+ using System . Collections . Generic ;
15+ using S1API . Logging ;
1116using UnityEngine ;
1217using Object = UnityEngine . Object ;
1318
@@ -23,6 +28,12 @@ namespace S1API.Items
2328 /// </remarks>
2429 public sealed class StorableItemDefinitionBuilder
2530 {
31+ private static readonly Log Logger = new Log ( "StorableItemDefinitionBuilder" ) ;
32+ private static readonly object StationItemGate = new object ( ) ;
33+ private static readonly Dictionary < int , S1StationFramework . StationItem > StationItemCache = new Dictionary < int , S1StationFramework . StationItem > ( ) ;
34+ private static readonly HashSet < int > WarnedStationItemModuleMissing = new HashSet < int > ( ) ;
35+ private static GameObject _stationItemRoot ;
36+
2637 private readonly S1ItemFramework . StorableItemDefinition _definition ;
2738 private readonly GameObject _storedItemPlaceholder ;
2839 private bool _hasCustomStoredItem ;
@@ -180,6 +191,43 @@ public StorableItemDefinitionBuilder WithStoredItem(GameObject storedItemPrefab)
180191 return this ;
181192 }
182193
194+ /// <summary>
195+ /// Assigns a StationItem prefab to this item definition so it can be used as a station/minigame ingredient
196+ /// (e.g., Chemistry Station).
197+ /// </summary>
198+ /// <remarks>
199+ /// S1API clones and caches the prefab under a hidden <c>DontDestroyOnLoad</c> root by default.
200+ /// This avoids mutating shared prefabs and helps keep the reference stable across scene loads.
201+ /// </remarks>
202+ /// <param name="stationItemPrefab">A prefab GameObject that has a StationItem component.</param>
203+ /// <returns>The builder instance for fluent chaining.</returns>
204+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="stationItemPrefab"/> is null.</exception>
205+ /// <exception cref="ArgumentException">Thrown if <paramref name="stationItemPrefab"/> does not have a StationItem component.</exception>
206+ public StorableItemDefinitionBuilder WithStationItem ( GameObject stationItemPrefab )
207+ {
208+ if ( stationItemPrefab == null )
209+ throw new ArgumentNullException ( nameof ( stationItemPrefab ) ) ;
210+
211+ var stationItem = stationItemPrefab . GetComponent < S1StationFramework . StationItem > ( ) ;
212+ if ( stationItem == null )
213+ throw new ArgumentException ( "Station item prefab must have a StationItem component." , nameof ( stationItemPrefab ) ) ;
214+
215+ var cached = GetOrCreateStationItemPrefab ( stationItem ) ;
216+ _definition . StationItem = cached ;
217+
218+ WarnIfStationItemMissingChemistryModules ( cached ) ;
219+ return this ;
220+ }
221+
222+ /// <summary>
223+ /// Clears the StationItem reference for this definition.
224+ /// </summary>
225+ public StorableItemDefinitionBuilder WithoutStationItem ( )
226+ {
227+ _definition . StationItem = null ;
228+ return this ;
229+ }
230+
183231 /// <summary>
184232 /// Sets whether this item is available in the demo version of the game.
185233 /// </summary>
@@ -221,6 +269,82 @@ internal S1ItemFramework.StorableItemDefinition BuildInternal()
221269 {
222270 return _definition ;
223271 }
272+
273+ private static S1StationFramework . StationItem GetOrCreateStationItemPrefab ( S1StationFramework . StationItem stationItemPrefab )
274+ {
275+ var id = stationItemPrefab . GetInstanceID ( ) ;
276+
277+ lock ( StationItemGate )
278+ {
279+ if ( StationItemCache . TryGetValue ( id , out var cached ) && cached != null )
280+ return cached ;
281+
282+ var root = GetStationItemRoot ( ) ;
283+
284+ // Clone + cache (final decision): keep a stable hidden prefab reference across scene loads.
285+ var clone = Object . Instantiate ( stationItemPrefab , root . transform ) ;
286+ clone . gameObject . hideFlags = HideFlags . HideAndDontSave ;
287+ clone . name = $ "{ stationItemPrefab . name } _S1API_StationItem";
288+
289+ // Keep the cache far away from gameplay so it doesn't interfere with scenes.
290+ clone . transform . position = root . transform . position ;
291+
292+ StationItemCache [ id ] = clone ;
293+ return clone ;
294+ }
295+ }
296+
297+ private static GameObject GetStationItemRoot ( )
298+ {
299+ if ( _stationItemRoot != null )
300+ return _stationItemRoot ;
301+
302+ lock ( StationItemGate )
303+ {
304+ if ( _stationItemRoot != null )
305+ return _stationItemRoot ;
306+
307+ var root = new GameObject ( "S1API_StationItemCache" ) ;
308+ root . hideFlags = HideFlags . HideAndDontSave ;
309+ Object . DontDestroyOnLoad ( root ) ;
310+
311+ // Place it far below the world; keep it active so instantiated prefabs remain active by default.
312+ root . transform . position = new Vector3 ( 0f , - 10000f , 0f ) ;
313+
314+ _stationItemRoot = root ;
315+ return root ;
316+ }
317+ }
318+
319+ private static void WarnIfStationItemMissingChemistryModules ( S1StationFramework . StationItem stationItemPrefab )
320+ {
321+ if ( stationItemPrefab == null )
322+ return ;
323+
324+ var id = stationItemPrefab . GetInstanceID ( ) ;
325+
326+ lock ( StationItemGate )
327+ {
328+ if ( ! WarnedStationItemModuleMissing . Add ( id ) )
329+ return ;
330+ }
331+
332+ try
333+ {
334+ var hasIngredientModule = stationItemPrefab . GetComponentInChildren < S1StationFramework . IngredientModule > ( true ) != null ;
335+ var hasPourableModule = stationItemPrefab . GetComponentInChildren < S1StationFramework . PourableModule > ( true ) != null ;
336+
337+ if ( hasIngredientModule || hasPourableModule )
338+ return ;
339+
340+ Logger . Warning (
341+ $ "[S1API] StationItem prefab '{ stationItemPrefab . name } ' does not contain an IngredientModule or PourableModule. " +
342+ "Chemistry station tasks may log errors or skip this ingredient at runtime." ) ;
343+ }
344+ catch
345+ {
346+ // best-effort warning only
347+ }
348+ }
224349 }
225350}
226-
0 commit comments