From 5ae185806dbffe6937e76d6b24d652e322e11076 Mon Sep 17 00:00:00 2001
From: k0 <21180271+k073l@users.noreply.github.com>
Date: Fri, 29 May 2026 16:55:25 +0200
Subject: [PATCH 1/7] refactor(items): cleanup qualityitembuilder
---
.../QualityItemDefinitionBuilder.cs | 93 ++++
.../StorableItemDefinitionBuilder.cs | 487 ++++++++++++++++++
S1API/Items/QualityItemDefinitionBuilder.cs | 3 +-
S1API/Items/StorableItemDefinitionBuilder.cs | 3 +-
4 files changed, 584 insertions(+), 2 deletions(-)
create mode 100644 S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs
create mode 100644 S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs
diff --git a/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs b/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs
new file mode 100644
index 0000000..5868188
--- /dev/null
+++ b/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs
@@ -0,0 +1,93 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+#endif
+using S1API.Internal.Utils;
+using S1API.Products;
+using UnityEngine;
+
+namespace S1API.Items.ItemBuilders
+{
+ ///
+ /// Builder for composing quality item definitions at runtime.
+ /// Use fluent methods to configure item properties before calling
+ ///
+ public sealed class QualityItemDefinitionBuilder
+ : StorableItemDefinitionBuilder
+ {
+ private S1ItemFramework.QualityItemDefinition QualityDefinition =>
+ CrossType.As(Definition);
+
+ ///
+ /// INTERNAL: Creates a new builder instance with a fresh QualityItemDefinition.
+ /// Only QualityItemCreator can instantiate this.
+ ///
+ internal QualityItemDefinitionBuilder()
+ : base(ScriptableObject.CreateInstance)
+ {
+ QualityDefinition.DefaultQuality =
+ S1ItemFramework.EQuality.Standard;
+ }
+
+ ///
+ /// INTERNAL: Creates a builder instance initialized by cloning an existing quality item definition.
+ /// Only QualityItemCreator can instantiate this.
+ ///
+ /// The existing quality item definition to clone properties from.
+ internal QualityItemDefinitionBuilder(
+ S1ItemFramework.QualityItemDefinition source)
+ : base(
+ source,
+ ScriptableObject.CreateInstance)
+ {
+ }
+
+ ///
+ protected override void CopyPropertiesFrom(
+ S1ItemFramework.StorableItemDefinition source)
+ {
+ base.CopyPropertiesFrom(source);
+
+ var qualitySource = CrossType.As(source);
+
+ QualityDefinition.DefaultQuality = qualitySource.DefaultQuality;
+ }
+
+ ///
+ /// Assigns a default quality for this definition.
+ ///
+ /// The default quality to assign to items of this definition.
+ /// >The builder instance for fluent chaining.
+ public QualityItemDefinitionBuilder WithDefaultQuality(Quality quality)
+ {
+ QualityDefinition.DefaultQuality = (S1ItemFramework.EQuality)quality;
+ return this;
+ }
+
+ ///
+ /// Builds the item definition, registers it with the game's registry, and returns a wrapper.
+ ///
+ /// A wrapper around the created quality item definition.
+ public new QualityItemDefinition Build()
+ {
+ return (QualityItemDefinition)base.Build();
+ }
+
+ ///
+ /// INTERNAL: Builds and returns the raw game item definition without registering.
+ /// Used internally by S1API. Modders should use instead.
+ ///
+ internal new S1ItemFramework.QualityItemDefinition BuildInternal()
+ {
+ return QualityDefinition;
+ }
+
+ ///
+ protected override StorableItemDefinition CreateWrapper(
+ S1ItemFramework.StorableItemDefinition definition)
+ {
+ return new QualityItemDefinition(QualityDefinition);
+ }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs b/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs
new file mode 100644
index 0000000..556f44b
--- /dev/null
+++ b/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs
@@ -0,0 +1,487 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework;
+using S1Levelling = Il2CppScheduleOne.Levelling;
+using S1Registry = Il2CppScheduleOne.Registry;
+using S1StationFramework = Il2CppScheduleOne.StationFramework;
+using S1Storage = Il2CppScheduleOne.Storage;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+using S1CoreItemFramework = ScheduleOne.Core.Items.Framework;
+using S1Levelling = ScheduleOne.Levelling;
+using S1Registry = ScheduleOne.Registry;
+using S1StationFramework = ScheduleOne.StationFramework;
+using S1Storage = ScheduleOne.Storage;
+#endif
+using System;
+using System.Collections.Generic;
+using S1API.Logging;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+namespace S1API.Items.ItemBuilders
+{
+ ///
+ /// Builder for composing item definitions at runtime.
+ /// Use fluent methods to configure item properties before calling .
+ ///
+ ///
+ /// All items in Schedule One are StorableItemDefinition (or subclasses thereof).
+ /// The base ItemDefinition class is never used directly in the game.
+ ///
+ public sealed class StorableItemDefinitionBuilder
+ : StorableItemDefinitionBuilder
+ {
+ ///
+ internal StorableItemDefinitionBuilder(
+ S1ItemFramework.StorableItemDefinition source)
+ : base(source)
+ {
+ }
+
+ ///
+ internal StorableItemDefinitionBuilder()
+ : base()
+ {
+ }
+
+ ///
+ /// Builds the item definition, registers it with the game's registry, and returns a wrapper.
+ ///
+ /// A wrapper around the created storable item definition.
+ public new StorableItemDefinition Build()
+ {
+ return base.Build();
+ }
+ }
+
+ ///
+ /// State class for managing shared resources and caches used by .
+ ///
+ internal static class StorableItemDefinitionBuilderState
+ {
+ internal static readonly Log Logger = new Log("StorableItemDefinitionBuilder");
+ internal static readonly object StationItemGate = new object();
+
+ internal static readonly Dictionary StationItemCache =
+ new Dictionary();
+
+ internal static readonly HashSet WarnedStationItemModuleMissing = new HashSet();
+ internal static GameObject _stationItemRoot;
+ }
+
+ ///
+ /// Generic base builder for composing item definitions at runtime, with fluent methods returning the correct subclass type.
+ ///
+ /// The concrete builder type being implemented (e.g., StorableItemDefinitionBuilder).
+ public abstract class StorableItemDefinitionBuilder
+ where TSelf : StorableItemDefinitionBuilder
+ {
+ private static Log Logger => StorableItemDefinitionBuilderState.Logger;
+ private static object StationItemGate => StorableItemDefinitionBuilderState.StationItemGate;
+
+ private static Dictionary StationItemCache =>
+ StorableItemDefinitionBuilderState.StationItemCache;
+
+ private static HashSet WarnedStationItemModuleMissing =>
+ StorableItemDefinitionBuilderState.WarnedStationItemModuleMissing;
+
+ private static GameObject StationItemRoot
+ {
+ get => StorableItemDefinitionBuilderState._stationItemRoot;
+ set => StorableItemDefinitionBuilderState._stationItemRoot = value;
+ }
+
+ ///
+ /// INTERNAL: The underlying game item definition being composed by this builder.
+ ///
+ protected readonly S1ItemFramework.StorableItemDefinition Definition;
+ private readonly GameObject _storedItemPlaceholder;
+ private bool _hasCustomStoredItem;
+
+ private TSelf Self => (TSelf)this;
+
+ ///
+ /// INTERNAL: Creates a new builder instance with a fresh StorableItemDefinition.
+ /// Only ItemCreator can instantiate this.
+ ///
+ internal StorableItemDefinitionBuilder(
+ Func? definitionFactory = null)
+ {
+ Definition = definitionFactory != null
+ ? definitionFactory()
+ : ScriptableObject.CreateInstance();
+
+ ApplyDefaults();
+
+ _storedItemPlaceholder = CreateStoredItemPlaceholder();
+ }
+
+ ///
+ /// INTERNAL: Creates a builder instance initialized by cloning an existing item.
+ /// Only ItemCreator can instantiate this.
+ ///
+ /// The existing item definition to clone properties from.
+ /// Optional factory function to create the definition instance. If null, a default StorableItemDefinition will be created.
+ internal StorableItemDefinitionBuilder(
+ S1ItemFramework.StorableItemDefinition source,
+ Func? definitionFactory = null)
+ : this(definitionFactory)
+ {
+ CopyPropertiesFrom(source);
+ }
+
+ ///
+ /// Copies all properties from a source definition to the current definition.
+ ///
+ /// The source definition to copy from.
+ protected virtual void CopyPropertiesFrom(S1ItemFramework.StorableItemDefinition source)
+ {
+ if (source == null) return;
+
+ // Basic ItemDefinition properties
+ Definition.Name = source.Name;
+ Definition.Description = source.Description;
+ Definition.Category = source.Category;
+ Definition.StackLimit = source.StackLimit;
+ Definition.AvailableInDemo = source.AvailableInDemo;
+ Definition.UsableInFilters = source.UsableInFilters;
+ Definition.Icon = source.Icon;
+ Definition.legalStatus = source.legalStatus;
+ Definition.PickpocketDifficultyMultiplier = source.PickpocketDifficultyMultiplier;
+ Definition.CombatUtility = source.CombatUtility;
+
+ // StorableItemDefinition properties
+ Definition.BasePurchasePrice = source.BasePurchasePrice;
+ Definition.ResellMultiplier = source.ResellMultiplier;
+ Definition.ShopCategories = source.ShopCategories;
+ Definition.RequiresLevelToPurchase = source.RequiresLevelToPurchase;
+ Definition.RequiredRank = source.RequiredRank;
+ Definition.StoredItem = source.StoredItem != null ? source.StoredItem : Definition.StoredItem;
+ Definition.StationItem = source.StationItem;
+ Definition.Equippable = source.Equippable;
+ Definition.CustomItemUI = source.CustomItemUI;
+ }
+
+ private void ApplyDefaults()
+ {
+ Definition.StackLimit = 10;
+ Definition.BasePurchasePrice = 10f;
+ Definition.ResellMultiplier = 0.5f;
+ Definition.Category = S1CoreItemFramework.EItemCategory.Tools;
+ Definition.legalStatus = S1CoreItemFramework.ELegalStatus.Legal;
+ Definition.AvailableInDemo = true;
+ Definition.UsableInFilters = true;
+ Definition.RequiresLevelToPurchase = false;
+ Definition.RequiredRank = new S1Levelling.FullRank(S1Levelling.ERank.Street_Rat, 1);
+ }
+
+ private GameObject CreateStoredItemPlaceholder()
+ {
+ var storedItemPlaceholder = new GameObject("S1API_DefaultStoredItem");
+ storedItemPlaceholder.SetActive(false);
+ storedItemPlaceholder.hideFlags = HideFlags.HideAndDontSave;
+ Object.DontDestroyOnLoad(storedItemPlaceholder);
+ var storedItemComponent = storedItemPlaceholder.AddComponent();
+ Definition.StoredItem = storedItemComponent;
+ return storedItemPlaceholder;
+ }
+
+ ///
+ /// Sets the basic information for the item.
+ ///
+ /// Unique identifier for the item (e.g., "my_custom_tool").
+ /// Display name shown in UI.
+ /// Item description shown in tooltips.
+ /// Item category for inventory organization.
+ /// The builder instance for fluent chaining.
+ public TSelf WithBasicInfo(string id, string name, string description, ItemCategory category)
+ {
+ Definition.ID = id;
+ Definition.Name = name;
+ Definition.Description = description;
+ Definition.Category = (S1CoreItemFramework.EItemCategory)category;
+
+ // Update the underlying ScriptableObject name for clarity in inspectors/debuggers.
+ var displayName = string.IsNullOrEmpty(name) ? id : name;
+ if (!string.IsNullOrEmpty(displayName))
+ {
+ Definition.name = displayName;
+ if (_storedItemPlaceholder != null && !_hasCustomStoredItem)
+ {
+ _storedItemPlaceholder.name = $"{displayName}_StoredItem";
+ }
+ }
+
+ return Self;
+ }
+
+ ///
+ /// Sets the maximum stack size for this item.
+ ///
+ /// Maximum quantity per inventory slot (1-999).
+ /// The builder instance for fluent chaining.
+ public TSelf WithStackLimit(int limit)
+ {
+ Definition.StackLimit = Mathf.Clamp(limit, 1, 999);
+ return Self;
+ }
+
+ ///
+ /// Sets the icon sprite displayed for this item in UI.
+ ///
+ /// The sprite to use as the item icon.
+ /// The builder instance for fluent chaining.
+ public TSelf WithIcon(Sprite icon)
+ {
+ Definition.Icon = icon;
+ return Self;
+ }
+
+ ///
+ /// Configures the economic properties of the item.
+ ///
+ /// Base price when buying from shops.
+ /// Fraction of purchase price recovered when selling (0.0 to 1.0).
+ /// The builder instance for fluent chaining.
+ public TSelf WithPricing(float basePurchasePrice, float resellMultiplier = 0.5f)
+ {
+ Definition.BasePurchasePrice = Mathf.Max(0f, basePurchasePrice);
+ Definition.ResellMultiplier = Mathf.Clamp01(resellMultiplier);
+ return Self;
+ }
+
+ ///
+ /// Sets the legal status of the item.
+ ///
+ /// Whether the item is legal or illegal.
+ /// The builder instance for fluent chaining.
+ public TSelf WithLegalStatus(LegalStatus status)
+ {
+ Definition.legalStatus = (S1CoreItemFramework.ELegalStatus)status;
+ return Self;
+ }
+
+ ///
+ /// Attaches an equippable component to this item, allowing it to be equipped by the player.
+ ///
+ /// The equippable wrapper to attach.
+ /// The builder instance for fluent chaining.
+ public TSelf WithEquippable(Equippable equippable)
+ {
+ if (equippable != null)
+ {
+ Definition.Equippable = equippable.S1Equippable;
+ }
+
+ return Self;
+ }
+
+ ///
+ /// Assigns a custom StoredItem prefab for this definition.
+ ///
+ /// Prefab containing a StoredItem component.
+ /// The builder instance for fluent chaining.
+ public TSelf WithStoredItem(GameObject storedItemPrefab)
+ {
+ if (storedItemPrefab == null)
+ return Self;
+
+ var storedItem = storedItemPrefab.GetComponent() ??
+ storedItemPrefab.AddComponent();
+ Definition.StoredItem = storedItem;
+ _hasCustomStoredItem = true;
+ return Self;
+ }
+
+ ///
+ /// Assigns a StationItem prefab to this item definition so it can be used as a station/minigame ingredient
+ /// (e.g., Chemistry Station).
+ ///
+ ///
+ /// S1API clones and caches the prefab under a hidden DontDestroyOnLoad root by default.
+ /// This avoids mutating shared prefabs and helps keep the reference stable across scene loads.
+ ///
+ /// A prefab GameObject that has a StationItem component.
+ /// The builder instance for fluent chaining.
+ /// Thrown if is null.
+ /// Thrown if does not have a StationItem component.
+ public TSelf WithStationItem(GameObject stationItemPrefab)
+ {
+ if (stationItemPrefab == null)
+ throw new ArgumentNullException(nameof(stationItemPrefab));
+
+ var stationItem = stationItemPrefab.GetComponent();
+ if (stationItem == null)
+ throw new ArgumentException("Station item prefab must have a StationItem component.",
+ nameof(stationItemPrefab));
+
+ var cached = GetOrCreateStationItemPrefab(stationItem);
+ Definition.StationItem = cached;
+
+ WarnIfStationItemMissingChemistryModules(cached);
+ return Self;
+ }
+
+ ///
+ /// Clears the StationItem reference for this definition.
+ ///
+ public TSelf WithoutStationItem()
+ {
+ Definition.StationItem = null;
+ return Self;
+ }
+
+ ///
+ /// Sets whether this item is available in the demo version of the game.
+ ///
+ /// True if available in demo, false otherwise.
+ /// The builder instance for fluent chaining.
+ public TSelf WithDemoAvailability(bool available)
+ {
+ Definition.AvailableInDemo = available;
+ return Self;
+ }
+
+ ///
+ /// Assigns a level requirement for purchasing this item in shops.
+ ///
+ /// The required rank to purchase this item, or null to remove level requirement.
+ /// >The builder instance for fluent chaining.
+ public TSelf WithRequiredRank(Leveling.FullRank? rank)
+ {
+ if (rank == null)
+ {
+ Definition.RequiresLevelToPurchase = false;
+ return Self;
+ }
+
+ Definition.RequiredRank = rank.Value.ToNative();
+ Definition.RequiresLevelToPurchase = true;
+ return Self;
+ }
+
+ ///
+ /// Builds the item definition, registers it with the game's registry, and returns a wrapper.
+ ///
+ /// A wrapper around the created storable item definition.
+ public virtual StorableItemDefinition Build()
+ {
+ if (!_hasCustomStoredItem && Definition.StoredItem != null)
+ {
+ // Ensure placeholder naming stays in sync after late changes.
+ if (!string.IsNullOrEmpty(Definition.Name) && _storedItemPlaceholder != null)
+ {
+ _storedItemPlaceholder.name = $"{Definition.Name}_StoredItem";
+ }
+ }
+
+ // Register with the game's registry
+ S1Registry.Instance.AddToRegistry(Definition);
+
+ // Return wrapper
+ return CreateWrapper(Definition);
+ }
+
+ ///
+ /// INTERNAL: Builds and returns the raw game item definition without registering.
+ /// Used internally by S1API. Modders should use instead.
+ ///
+ internal virtual S1ItemFramework.StorableItemDefinition BuildInternal()
+ {
+ return Definition;
+ }
+
+ ///
+ /// Creates a wrapper around the given item definition.
+ /// Subclasses can override this to return a more specific wrapper type.
+ ///
+ /// The item definition to wrap.
+ /// >A wrapper around the given item definition.
+ protected virtual StorableItemDefinition CreateWrapper(
+ S1ItemFramework.StorableItemDefinition definition)
+ {
+ return new StorableItemDefinition(definition);
+ }
+
+ private static S1StationFramework.StationItem GetOrCreateStationItemPrefab(
+ S1StationFramework.StationItem stationItemPrefab)
+ {
+ var id = stationItemPrefab.GetInstanceID();
+
+ lock (StationItemGate)
+ {
+ if (StationItemCache.TryGetValue(id, out var cached) && cached != null)
+ return cached;
+
+ var root = GetStationItemRoot();
+
+ // Clone + cache (final decision): keep a stable hidden prefab reference across scene loads.
+ var clone = Object.Instantiate(stationItemPrefab, root.transform);
+ clone.gameObject.hideFlags = HideFlags.HideAndDontSave;
+ clone.name = $"{stationItemPrefab.name}_S1API_StationItem";
+
+ // Keep the cache far away from gameplay so it doesn't interfere with scenes.
+ clone.transform.position = root.transform.position;
+
+ StationItemCache[id] = clone;
+ return clone;
+ }
+ }
+
+ private static GameObject GetStationItemRoot()
+ {
+ if (StationItemRoot != null)
+ return StationItemRoot;
+
+ lock (StationItemGate)
+ {
+ if (StationItemRoot != null)
+ return StationItemRoot;
+
+ var root = new GameObject("S1API_StationItemCache");
+ root.hideFlags = HideFlags.HideAndDontSave;
+ Object.DontDestroyOnLoad(root);
+
+ // Place it far below the world; keep it active so instantiated prefabs remain active by default.
+ root.transform.position = new Vector3(0f, -10000f, 0f);
+
+ StationItemRoot = root;
+ return root;
+ }
+ }
+
+ private static void WarnIfStationItemMissingChemistryModules(S1StationFramework.StationItem stationItemPrefab)
+ {
+ if (stationItemPrefab == null)
+ return;
+
+ var id = stationItemPrefab.GetInstanceID();
+
+ lock (StationItemGate)
+ {
+ if (!WarnedStationItemModuleMissing.Add(id))
+ return;
+ }
+
+ try
+ {
+ var hasIngredientModule =
+ stationItemPrefab.GetComponentInChildren(true) != null;
+ var hasPourableModule =
+ stationItemPrefab.GetComponentInChildren(true) != null;
+
+ if (hasIngredientModule || hasPourableModule)
+ return;
+
+ Logger.Warning(
+ $"[S1API] StationItem prefab '{stationItemPrefab.name}' does not contain an IngredientModule or PourableModule. " +
+ "Chemistry station tasks may log errors or skip this ingredient at runtime.");
+ }
+ catch
+ {
+ // best-effort warning only
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/QualityItemDefinitionBuilder.cs b/S1API/Items/QualityItemDefinitionBuilder.cs
index db7fd0f..1255313 100644
--- a/S1API/Items/QualityItemDefinitionBuilder.cs
+++ b/S1API/Items/QualityItemDefinitionBuilder.cs
@@ -27,6 +27,7 @@ namespace S1API.Items
/// Builder for composing quality item definitions at runtime.
/// Use fluent methods to configure item properties before calling
///
+ [Obsolete]
public sealed class QualityItemDefinitionBuilder
{
private static readonly Log Logger = new Log("QualityItemDefinitionBuilder");
@@ -409,4 +410,4 @@ private static void WarnIfStationItemMissingChemistryModules(S1StationFramework.
}
}
}
-}
+}
\ No newline at end of file
diff --git a/S1API/Items/StorableItemDefinitionBuilder.cs b/S1API/Items/StorableItemDefinitionBuilder.cs
index 91c66a1..867099b 100644
--- a/S1API/Items/StorableItemDefinitionBuilder.cs
+++ b/S1API/Items/StorableItemDefinitionBuilder.cs
@@ -1,4 +1,4 @@
-#if (IL2CPPMELON)
+#if (IL2CPPMELON)
using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework;
using S1Levelling = Il2CppScheduleOne.Levelling;
@@ -30,6 +30,7 @@ namespace S1API.Items
/// All items in Schedule One are StorableItemDefinition (or subclasses thereof).
/// The base ItemDefinition class is never used directly in the game.
///
+ [Obsolete]
public sealed class StorableItemDefinitionBuilder
{
private static readonly Log Logger = new Log("StorableItemDefinitionBuilder");
From 7835ddb3893b685bb41f7ee193821cf658fbd62b Mon Sep 17 00:00:00 2001
From: k0 <21180271+k073l@users.noreply.github.com>
Date: Fri, 29 May 2026 19:04:57 +0200
Subject: [PATCH 2/7] refactor(items): cleanup additive, buildable, clothing
builders
---
S1API/Items/Additive/AdditiveDefinition.cs | 57 ++++
S1API/Items/Additive/AdditiveItemCreator.cs | 66 +++++
S1API/Items/AdditiveDefinition.cs | 2 +
S1API/Items/AdditiveDefinitionBuilder.cs | 1 +
S1API/Items/AdditiveItemCreator.cs | 1 +
S1API/Items/Buildable/BuildableItemCreator.cs | 104 +++++++
.../Buildable/BuildableItemDefinition.cs | 59 ++++
S1API/Items/BuildableItemCreator.cs | 2 +
S1API/Items/BuildableItemDefinition.cs | 2 +
S1API/Items/BuildableItemDefinitionBuilder.cs | 2 +
.../Items/Clothing/ClothingApplicationType.cs | 18 ++
S1API/Items/Clothing/ClothingColor.cs | 39 +++
S1API/Items/Clothing/ClothingItemCreator.cs | 105 +++++++
.../Items/Clothing/ClothingItemDefinition.cs | 144 +++++++++
S1API/Items/Clothing/ClothingItemInstance.cs | 46 +++
S1API/Items/Clothing/ClothingSlot.cs | 32 ++
S1API/Items/ClothingApplicationType.cs | 3 +
S1API/Items/ClothingColor.cs | 3 +
S1API/Items/ClothingItemCreator.cs | 2 +
S1API/Items/ClothingItemDefinition.cs | 2 +
S1API/Items/ClothingItemDefinitionBuilder.cs | 2 +
S1API/Items/ClothingItemInstance.cs | 2 +
S1API/Items/ClothingSlot.cs | 3 +
.../ItemBuilders/AdditiveDefinitionBuilder.cs | 141 +++++++++
.../BuildableItemDefinitionBuilder.cs | 91 ++++++
.../ClothingItemDefinitionBuilder.cs | 276 ++++++++++++++++++
.../QualityItemDefinitionBuilder.cs | 10 +-
.../StorableItemDefinitionBuilder.cs | 8 +-
S1API/Items/ItemCreator.cs | 1 +
S1API/Items/ItemManager.cs | 49 ++++
S1API/Items/Quality/QualityItemCreator.cs | 67 +++++
S1API/Items/Quality/QualityItemDefinition.cs | 66 +++++
S1API/Items/Quality/QualityItemInstance.cs | 44 +++
S1API/Items/QualityItemCreator.cs | 1 +
S1API/Items/QualityItemDefinition.cs | 10 +-
S1API/Items/QualityItemDefinitionBuilder.cs | 4 +-
S1API/Items/QualityItemInstance.cs | 6 +-
S1API/Items/Storable/ItemCreator.cs | 163 +++++++++++
.../Items/Storable/StorableItemDefinition.cs | 94 ++++++
S1API/Items/StorableItemDefinition.cs | 2 +
S1API/Items/StorableItemDefinitionBuilder.cs | 2 +-
41 files changed, 1714 insertions(+), 18 deletions(-)
create mode 100644 S1API/Items/Additive/AdditiveDefinition.cs
create mode 100644 S1API/Items/Additive/AdditiveItemCreator.cs
create mode 100644 S1API/Items/Buildable/BuildableItemCreator.cs
create mode 100644 S1API/Items/Buildable/BuildableItemDefinition.cs
create mode 100644 S1API/Items/Clothing/ClothingApplicationType.cs
create mode 100644 S1API/Items/Clothing/ClothingColor.cs
create mode 100644 S1API/Items/Clothing/ClothingItemCreator.cs
create mode 100644 S1API/Items/Clothing/ClothingItemDefinition.cs
create mode 100644 S1API/Items/Clothing/ClothingItemInstance.cs
create mode 100644 S1API/Items/Clothing/ClothingSlot.cs
create mode 100644 S1API/Items/ItemBuilders/AdditiveDefinitionBuilder.cs
create mode 100644 S1API/Items/ItemBuilders/BuildableItemDefinitionBuilder.cs
create mode 100644 S1API/Items/ItemBuilders/ClothingItemDefinitionBuilder.cs
create mode 100644 S1API/Items/Quality/QualityItemCreator.cs
create mode 100644 S1API/Items/Quality/QualityItemDefinition.cs
create mode 100644 S1API/Items/Quality/QualityItemInstance.cs
create mode 100644 S1API/Items/Storable/ItemCreator.cs
create mode 100644 S1API/Items/Storable/StorableItemDefinition.cs
diff --git a/S1API/Items/Additive/AdditiveDefinition.cs b/S1API/Items/Additive/AdditiveDefinition.cs
new file mode 100644
index 0000000..cc1fc2d
--- /dev/null
+++ b/S1API/Items/Additive/AdditiveDefinition.cs
@@ -0,0 +1,57 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+#endif
+using System;
+using UnityEngine;
+
+namespace S1API.Items.Additive
+{
+ ///
+ /// Represents an additive item definition.
+ /// Extends with additive-specific properties.
+ ///
+ ///
+ /// Builder-only: these properties are intentionally read-only to avoid runtime surprises from mutating
+ /// globally-registered ScriptableObject definitions mid-session. Use to create
+ /// additives with configured effects.
+ ///
+ public sealed class AdditiveDefinition : Storable.StorableItemDefinition
+ {
+ ///
+ /// INTERNAL: Wraps an existing native additive definition.
+ ///
+ internal AdditiveDefinition(S1ItemFramework.AdditiveDefinition definition)
+ : base(definition)
+ {
+ S1AdditiveDefinition = definition;
+ }
+
+ ///
+ /// INTERNAL: A reference to the native game additive definition.
+ ///
+ internal S1ItemFramework.AdditiveDefinition S1AdditiveDefinition { get; }
+
+ ///
+ /// Display material used for the additive (if applicable).
+ ///
+ public Material DisplayMaterial => S1AdditiveDefinition.DisplayMaterial;
+
+ ///
+ /// Quality modifier applied by this additive.
+ ///
+ public float QualityChange => S1AdditiveDefinition.QualityChange;
+
+ ///
+ /// Yield multiplier applied by this additive.
+ ///
+ public float YieldMultiplier => S1AdditiveDefinition.YieldMultiplier;
+
+ ///
+ /// Instant growth fraction applied by this additive (0..1).
+ ///
+ public float InstantGrowth => S1AdditiveDefinition.InstantGrowth;
+ }
+}
+
diff --git a/S1API/Items/Additive/AdditiveItemCreator.cs b/S1API/Items/Additive/AdditiveItemCreator.cs
new file mode 100644
index 0000000..3b03251
--- /dev/null
+++ b/S1API/Items/Additive/AdditiveItemCreator.cs
@@ -0,0 +1,66 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+using S1Registry = Il2CppScheduleOne.Registry;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+using S1Registry = ScheduleOne.Registry;
+#endif
+using System;
+using S1API.Internal.Utils;
+
+namespace S1API.Items.Additive
+{
+ ///
+ /// Provides convenient static methods for creating custom additive items.
+ /// Use for creating additives from scratch, or for variants.
+ ///
+ public static class AdditiveItemCreator
+ {
+ ///
+ /// Creates a new builder for composing an additive definition with full flexibility.
+ /// Use fluent methods to configure the additive, then call Build() to register it.
+ ///
+ public static ItemBuilders.AdditiveDefinitionBuilder CreateBuilder()
+ {
+ return new ItemBuilders.AdditiveDefinitionBuilder();
+ }
+
+ ///
+ /// Creates a new additive builder by cloning an existing additive by ID.
+ ///
+ /// The ID of the additive to clone.
+ /// A builder pre-configured with the source additive's properties.
+ /// Thrown if the source item does not exist or is not an additive.
+ public static ItemBuilders.AdditiveDefinitionBuilder CloneFrom(string sourceItemId)
+ {
+ var sourceDefinition = S1Registry.GetItem(sourceItemId);
+ if (sourceDefinition == null)
+ {
+ throw new ArgumentException($"Source item with ID '{sourceItemId}' not found in registry", nameof(sourceItemId));
+ }
+
+ if (!CrossType.Is(sourceDefinition, out S1ItemFramework.AdditiveDefinition additiveDef))
+ {
+ throw new ArgumentException($"Item '{sourceItemId}' is not an AdditiveDefinition", nameof(sourceItemId));
+ }
+
+ return new ItemBuilders.AdditiveDefinitionBuilder(additiveDef);
+ }
+
+ ///
+ /// Creates a new additive builder by cloning an existing additive wrapper.
+ ///
+ /// The additive definition to clone from.
+ /// A builder pre-configured with the source additive's properties.
+ public static ItemBuilders.AdditiveDefinitionBuilder CloneFrom(AdditiveDefinition source)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source), "Source additive definition cannot be null");
+ }
+
+ return new ItemBuilders.AdditiveDefinitionBuilder(source.S1AdditiveDefinition);
+ }
+ }
+}
+
diff --git a/S1API/Items/AdditiveDefinition.cs b/S1API/Items/AdditiveDefinition.cs
index f7ebf5c..70882ca 100644
--- a/S1API/Items/AdditiveDefinition.cs
+++ b/S1API/Items/AdditiveDefinition.cs
@@ -4,6 +4,7 @@
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
+using System;
using UnityEngine;
namespace S1API.Items
@@ -17,6 +18,7 @@ namespace S1API.Items
/// globally-registered ScriptableObject definitions mid-session. Use to create
/// additives with configured effects.
///
+ [Obsolete("Use S1API.Items.Additive.AdditiveDefinition instead")]
public sealed class AdditiveDefinition : StorableItemDefinition
{
///
diff --git a/S1API/Items/AdditiveDefinitionBuilder.cs b/S1API/Items/AdditiveDefinitionBuilder.cs
index d78cb39..86c3e76 100644
--- a/S1API/Items/AdditiveDefinitionBuilder.cs
+++ b/S1API/Items/AdditiveDefinitionBuilder.cs
@@ -22,6 +22,7 @@ namespace S1API.Items
/// Builder for composing additive definitions at runtime.
/// Use fluent methods to configure additive properties before calling .
///
+ [Obsolete("Use S1API.Items.ItemBuilders.AdditiveDefinitionBuilder instead")]
public sealed class AdditiveDefinitionBuilder
{
private static readonly Log Logger = new Log("AdditiveDefinitionBuilder");
diff --git a/S1API/Items/AdditiveItemCreator.cs b/S1API/Items/AdditiveItemCreator.cs
index 18d0523..6e87d8b 100644
--- a/S1API/Items/AdditiveItemCreator.cs
+++ b/S1API/Items/AdditiveItemCreator.cs
@@ -15,6 +15,7 @@ namespace S1API.Items
/// Provides convenient static methods for creating custom additive items.
/// Use for creating additives from scratch, or for variants.
///
+ [Obsolete("Use S1API.Items.Additive.AdditiveItemCreator instead")]
public static class AdditiveItemCreator
{
///
diff --git a/S1API/Items/Buildable/BuildableItemCreator.cs b/S1API/Items/Buildable/BuildableItemCreator.cs
new file mode 100644
index 0000000..125d781
--- /dev/null
+++ b/S1API/Items/Buildable/BuildableItemCreator.cs
@@ -0,0 +1,104 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+using S1Registry = Il2CppScheduleOne.Registry;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+using S1Registry = ScheduleOne.Registry;
+#endif
+using System;
+using S1API.Internal.Utils;
+
+namespace S1API.Items.Buildable
+{
+ ///
+ /// Provides convenient static methods for creating custom buildable items.
+ ///
+ ///
+ /// Use for creating items from scratch,
+ /// or for creating variants of existing buildable items.
+ ///
+ public static class BuildableItemCreator
+ {
+ ///
+ /// Creates a new builder for composing a buildable item definition with full flexibility.
+ /// Use fluent methods to configure the item, then call Build() to register it.
+ ///
+ /// A new BuildableItemDefinitionBuilder instance for fluent configuration.
+ ///
+ ///
+ /// var item = BuildableItemCreator.CreateBuilder()
+ /// .WithBasicInfo("my_rack", "Custom Storage Rack", "A custom storage rack")
+ /// .WithBuildSound(BuildSoundType.Metal)
+ /// .WithPricing(75f, 0.5f)
+ /// .Build();
+ ///
+ ///
+ public static ItemBuilders.BuildableItemDefinitionBuilder CreateBuilder()
+ {
+ return new ItemBuilders.BuildableItemDefinitionBuilder();
+ }
+
+ ///
+ /// Creates a new buildable item by cloning an existing item's properties.
+ /// This is useful for creating variants of existing items (e.g., different materials, sizes).
+ ///
+ /// The ID of the existing buildable item to clone from.
+ /// A builder initialized with the source item's properties, ready for customization.
+ ///
+ ///
+ /// var metalRack = BuildableItemCreator.CloneFrom("StorageRack-1x0.5")
+ /// .WithBasicInfo("metal_rack_small", "Small Metal Storage Rack", "A metal version")
+ /// .WithBuildSound(BuildSoundType.Metal)
+ /// .WithPricing(72f, 0.5f)
+ /// .Build();
+ ///
+ ///
+ public static ItemBuilders.BuildableItemDefinitionBuilder CloneFrom(string sourceItemId)
+ {
+ var sourceDefinition = S1Registry.GetItem(sourceItemId);
+
+ if (sourceDefinition == null)
+ {
+ throw new System.ArgumentException(
+ $"Source item with ID '{sourceItemId}' not found in registry",
+ nameof(sourceItemId)
+ );
+ }
+
+ // Try to cast to BuildableItemDefinition
+ if (!CrossType.Is(sourceDefinition, out S1ItemFramework.BuildableItemDefinition buildableDef))
+ {
+ throw new System.ArgumentException(
+ $"Item '{sourceItemId}' is not a BuildableItemDefinition",
+ nameof(sourceItemId)
+ );
+ }
+
+ return new ItemBuilders.BuildableItemDefinitionBuilder(buildableDef);
+ }
+
+ ///
+ /// Creates a new buildable item by cloning from an existing BuildableItemDefinition wrapper.
+ ///
+ /// The buildable item definition to clone from.
+ /// A builder initialized with the source item's properties, ready for customization.
+ ///
+ ///
+ /// var originalRack = ItemManager.GetItemDefinition("StorageRack-1x0.5") as BuildableItemDefinition;
+ /// var metalRack = BuildableItemCreator.CloneFrom(originalRack)
+ /// .WithBasicInfo("metal_rack_small", "Small Metal Storage Rack", "A metal version")
+ /// .WithBuildSound(BuildSoundType.Metal)
+ /// .Build();
+ ///
+ ///
+ public static ItemBuilders.BuildableItemDefinitionBuilder CloneFrom(BuildableItemDefinition source)
+ {
+ if (source == null)
+ {
+ throw new System.ArgumentNullException(nameof(source), "Source item definition cannot be null");
+ }
+
+ return new ItemBuilders.BuildableItemDefinitionBuilder(source.S1BuildableItemDefinition);
+ }
+ }
+}
diff --git a/S1API/Items/Buildable/BuildableItemDefinition.cs b/S1API/Items/Buildable/BuildableItemDefinition.cs
new file mode 100644
index 0000000..8565cd0
--- /dev/null
+++ b/S1API/Items/Buildable/BuildableItemDefinition.cs
@@ -0,0 +1,59 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+#endif
+using System;
+
+namespace S1API.Items.Buildable
+{
+ ///
+ /// Represents a buildable item definition that can be placed in the game world.
+ /// Extends with building-specific properties.
+ ///
+ ///
+ /// Use to create new buildable items,
+ /// or to create variants of existing items.
+ ///
+ public sealed class BuildableItemDefinition : Storable.StorableItemDefinition
+ {
+ ///
+ /// INTERNAL: Wraps an existing native buildable item definition.
+ ///
+ internal BuildableItemDefinition(S1ItemFramework.BuildableItemDefinition definition)
+ : base(definition)
+ {
+ S1BuildableItemDefinition = definition;
+ }
+
+ ///
+ /// INTERNAL: A reference to the native game buildable item definition.
+ ///
+ internal S1ItemFramework.BuildableItemDefinition S1BuildableItemDefinition { get; }
+
+ ///
+ /// The sound type played when this item is built.
+ ///
+ public BuildSoundType BuildSoundType
+ {
+ get => (BuildSoundType)S1BuildableItemDefinition.BuildSoundType;
+ set => S1BuildableItemDefinition.BuildSoundType = (S1ItemFramework.BuildableItemDefinition.EBuildSoundType)value;
+ }
+
+ }
+
+ ///
+ /// Specifies the sound type played when a buildable item is placed.
+ ///
+ public enum BuildSoundType
+ {
+ /// Wood building sound.
+ Wood,
+ /// Metal building sound.
+ Metal,
+ /// Plastic building sound.
+ Plastic,
+ /// Cardboard building sound.
+ Cardboard
+ }
+}
diff --git a/S1API/Items/BuildableItemCreator.cs b/S1API/Items/BuildableItemCreator.cs
index e8fe4bd..7d00446 100644
--- a/S1API/Items/BuildableItemCreator.cs
+++ b/S1API/Items/BuildableItemCreator.cs
@@ -6,6 +6,7 @@
using S1Registry = ScheduleOne.Registry;
#endif
+using System;
using S1API.Internal.Utils;
namespace S1API.Items
@@ -17,6 +18,7 @@ namespace S1API.Items
/// Use for creating items from scratch,
/// or for creating variants of existing buildable items.
///
+ [Obsolete]
public static class BuildableItemCreator
{
///
diff --git a/S1API/Items/BuildableItemDefinition.cs b/S1API/Items/BuildableItemDefinition.cs
index 65b0ca6..77c4fa9 100644
--- a/S1API/Items/BuildableItemDefinition.cs
+++ b/S1API/Items/BuildableItemDefinition.cs
@@ -4,6 +4,7 @@
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
+using System;
using UnityEngine;
namespace S1API.Items
@@ -16,6 +17,7 @@ namespace S1API.Items
/// Use to create new buildable items,
/// or to create variants of existing items.
///
+ [Obsolete]
public sealed class BuildableItemDefinition : StorableItemDefinition
{
///
diff --git a/S1API/Items/BuildableItemDefinitionBuilder.cs b/S1API/Items/BuildableItemDefinitionBuilder.cs
index 3c4d1a8..bafe1b8 100644
--- a/S1API/Items/BuildableItemDefinitionBuilder.cs
+++ b/S1API/Items/BuildableItemDefinitionBuilder.cs
@@ -8,6 +8,7 @@
using S1Registry = ScheduleOne.Registry;
#endif
+using System;
using UnityEngine;
namespace S1API.Items
@@ -16,6 +17,7 @@ namespace S1API.Items
/// Builder for composing buildable item definitions at runtime.
/// Use fluent methods to configure buildable item properties before calling .
///
+ [Obsolete]
public sealed class BuildableItemDefinitionBuilder
{
private readonly S1ItemFramework.BuildableItemDefinition _definition;
diff --git a/S1API/Items/Clothing/ClothingApplicationType.cs b/S1API/Items/Clothing/ClothingApplicationType.cs
new file mode 100644
index 0000000..3b6aaf0
--- /dev/null
+++ b/S1API/Items/Clothing/ClothingApplicationType.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace S1API.Items.Clothing
+{
+ ///
+ /// Represents how a clothing item is applied to the avatar.
+ /// Mirrors ScheduleOne.Clothing.EClothingApplicationType.
+ ///
+ public enum ClothingApplicationType
+ {
+ /// Applied as a body layer (flat texture on body mesh).
+ BodyLayer = 0,
+ /// Applied as a face layer (flat texture on face mesh).
+ FaceLayer = 1,
+ /// Applied as a 3D accessory (separate mesh).
+ Accessory = 2
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/Clothing/ClothingColor.cs b/S1API/Items/Clothing/ClothingColor.cs
new file mode 100644
index 0000000..8fedc80
--- /dev/null
+++ b/S1API/Items/Clothing/ClothingColor.cs
@@ -0,0 +1,39 @@
+using System;
+
+namespace S1API.Items.Clothing
+{
+ ///
+ /// Represents available clothing colors.
+ /// Mirrors ScheduleOne.Clothing.EClothingColor.
+ ///
+ public enum ClothingColor
+ {
+ White = 0,
+ LightGrey = 1,
+ DarkGrey = 2,
+ Charcoal = 3,
+ Black = 4,
+ LightRed = 5,
+ Red = 6,
+ Crimson = 7,
+ Orange = 8,
+ Tan = 9,
+ Brown = 10,
+ Coral = 11,
+ Beige = 12,
+ Yellow = 13,
+ Lime = 14,
+ LightGreen = 15,
+ DarkGreen = 16,
+ Cyan = 17,
+ SkyBlue = 18,
+ Blue = 19,
+ DeepBlue = 20,
+ Navy = 21,
+ DeepPurple = 22,
+ Purple = 23,
+ Magenta = 24,
+ BrightPink = 25,
+ HotPink = 26
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/Clothing/ClothingItemCreator.cs b/S1API/Items/Clothing/ClothingItemCreator.cs
new file mode 100644
index 0000000..517814f
--- /dev/null
+++ b/S1API/Items/Clothing/ClothingItemCreator.cs
@@ -0,0 +1,105 @@
+#if (IL2CPPMELON)
+using S1 = Il2CppScheduleOne;
+using S1Clothing = Il2CppScheduleOne.Clothing;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1 = ScheduleOne;
+using S1Clothing = ScheduleOne.Clothing;
+#endif
+using System;
+using S1API.Internal.Utils;
+using S1API.Logging;
+
+namespace S1API.Items.Clothing
+{
+ ///
+ /// Provides convenient static methods for creating custom clothing items.
+ /// Use for flexible configuration or for variants.
+ ///
+ public static class ClothingItemCreator
+ {
+ private static readonly Log Logger = new Log("ClothingItemCreator");
+
+ ///
+ /// Creates a new builder for composing a clothing item definition with full flexibility.
+ /// Use fluent methods to configure the item, then call Build() to register it.
+ ///
+ /// A new ClothingItemDefinitionBuilder instance for fluent configuration.
+ ///
+ ///
+ /// var hat = ClothingItemCreator.CreateBuilder()
+ /// .WithBasicInfo("my_hat", "Custom Hat", "A fancy custom hat")
+ /// .WithSlot(ClothingSlot.Head)
+ /// .WithApplicationType(ClothingApplicationType.Accessory)
+ /// .WithClothingAsset("MyMod/Accessories/CustomHat")
+ /// .WithDefaultColor(ClothingColor.Black)
+ /// .Build();
+ ///
+ ///
+ public static ItemBuilders.ClothingItemDefinitionBuilder CreateBuilder()
+ {
+ return new ItemBuilders.ClothingItemDefinitionBuilder();
+ }
+
+ ///
+ /// Creates a new clothing item by cloning an existing one by ID.
+ /// Returns a builder pre-configured with all properties of the source item.
+ /// You can then override specific properties before calling Build().
+ ///
+ /// The ID of the clothing item to clone.
+ /// A builder pre-configured with the source item's properties.
+ ///
+ ///
+ /// // Clone the base game cap and customize it
+ /// var customCap = ClothingItemCreator.CloneFrom("cap")
+ /// .WithBasicInfo("stay_silly_cap", "Stay Silly Cap", "A silly custom cap")
+ /// .WithClothingAsset("BigWillyMod/Accessories/StaySillyCap")
+ /// .WithColorable(false)
+ /// .Build();
+ ///
+ ///
+ public static ItemBuilders.ClothingItemDefinitionBuilder CloneFrom(string sourceItemId)
+ {
+ var sourceDefinition = S1.Registry.GetItem(sourceItemId);
+ if (sourceDefinition == null)
+ {
+ Logger.Error($"Cannot clone clothing item '{sourceItemId}': source item not found in registry");
+ return null;
+ }
+
+ // Use CrossType for proper IL2CPP/Mono type checking
+ if (!CrossType.Is(sourceDefinition, out S1Clothing.ClothingDefinition clothingDef))
+ {
+ Logger.Error($"Cannot clone item '{sourceItemId}': it is not a clothing item (found type: {sourceDefinition.GetType().Name})");
+ return null;
+ }
+
+ return new ItemBuilders.ClothingItemDefinitionBuilder(clothingDef);
+ }
+
+ ///
+ /// Creates a new clothing item by cloning an existing one.
+ /// Returns a builder pre-configured with all properties of the source item.
+ ///
+ /// The clothing item definition to clone.
+ /// A builder pre-configured with the source item's properties.
+ ///
+ ///
+ /// var existingCap = ItemManager.GetItemDefinition("cap") as ClothingItemDefinition;
+ /// var variant = ClothingItemCreator.CloneFrom(existingCap)
+ /// .WithBasicInfo("variant_cap", "Cap Variant", "A variant of the cap")
+ /// .Build();
+ ///
+ ///
+ public static ItemBuilders.ClothingItemDefinitionBuilder CloneFrom(ClothingItemDefinition source)
+ {
+ if (source == null)
+ {
+ Logger.Error("Cannot clone from null clothing item definition");
+ return null;
+ }
+
+ return new ItemBuilders.ClothingItemDefinitionBuilder(source.S1ClothingDefinition);
+ }
+ }
+}
+
diff --git a/S1API/Items/Clothing/ClothingItemDefinition.cs b/S1API/Items/Clothing/ClothingItemDefinition.cs
new file mode 100644
index 0000000..2a58ffc
--- /dev/null
+++ b/S1API/Items/Clothing/ClothingItemDefinition.cs
@@ -0,0 +1,144 @@
+#if (IL2CPPMELON)
+using S1Clothing = Il2CppScheduleOne.Clothing;
+using Il2CppCollections = Il2CppSystem.Collections.Generic;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1Clothing = ScheduleOne.Clothing;
+#endif
+using System;
+using System.Collections.Generic;
+
+namespace S1API.Items.Clothing
+{
+ ///
+ /// Represents a clothing item definition that can be worn by the player.
+ /// Extends with clothing-specific properties.
+ ///
+ ///
+ /// Use to create new clothing items,
+ /// or to create variants of existing items.
+ ///
+ public sealed class ClothingItemDefinition : Storable.StorableItemDefinition
+ {
+ ///
+ /// INTERNAL: Wraps an existing native clothing item definition.
+ ///
+ internal ClothingItemDefinition(S1Clothing.ClothingDefinition definition)
+ : base(definition)
+ {
+ S1ClothingDefinition = definition;
+ }
+
+ ///
+ /// INTERNAL: A reference to the native game clothing item definition.
+ ///
+ internal S1Clothing.ClothingDefinition S1ClothingDefinition { get; }
+
+ ///
+ /// Creates a clothing instance from this definition using the default color.
+ ///
+ /// The quantity to apply to the created clothing instance.
+ /// A clothing item instance using this definition's default color.
+ public override ItemInstance CreateInstance(int quantity = 1) =>
+ CreateInstance(quantity, DefaultColor);
+
+ ///
+ /// Creates a clothing instance from this definition with the specified color.
+ ///
+ /// The clothing color to apply to the created instance.
+ /// A clothing instance using the specified color.
+ public ClothingItemInstance CreateInstance(ClothingColor color) =>
+ CreateInstance(1, color);
+
+ ///
+ /// Creates a clothing instance from this definition with the specified quantity and color.
+ ///
+ /// The quantity to apply to the created clothing instance.
+ /// The clothing color to apply to the created instance.
+ /// A clothing instance using the specified quantity and color.
+ public ClothingItemInstance CreateInstance(int quantity, ClothingColor color) =>
+ new ClothingItemInstance(new S1Clothing.ClothingInstance(
+ S1ClothingDefinition,
+ quantity,
+ (S1Clothing.EClothingColor)color));
+
+ ///
+ /// The clothing slot this item occupies.
+ ///
+ public ClothingSlot Slot
+ {
+ get => (ClothingSlot)S1ClothingDefinition.Slot;
+ set => S1ClothingDefinition.Slot = (S1Clothing.EClothingSlot)value;
+ }
+
+ ///
+ /// How this clothing item is applied to the avatar.
+ ///
+ public ClothingApplicationType ApplicationType
+ {
+ get => (ClothingApplicationType)S1ClothingDefinition.ApplicationType;
+ set => S1ClothingDefinition.ApplicationType = (S1Clothing.EClothingApplicationType)value;
+ }
+
+ ///
+ /// The asset path to the clothing prefab or layer in Resources.
+ ///
+ public string ClothingAssetPath
+ {
+ get => S1ClothingDefinition.ClothingAssetPath;
+ set => S1ClothingDefinition.ClothingAssetPath = value;
+ }
+
+ ///
+ /// Whether this clothing item can be colored by the player.
+ ///
+ public bool Colorable
+ {
+ get => S1ClothingDefinition.Colorable;
+ set => S1ClothingDefinition.Colorable = value;
+ }
+
+ ///
+ /// The default color for this clothing item.
+ ///
+ public ClothingColor DefaultColor
+ {
+ get => (ClothingColor)S1ClothingDefinition.DefaultColor;
+ set => S1ClothingDefinition.DefaultColor = (S1Clothing.EClothingColor)value;
+ }
+
+ ///
+ /// List of clothing slots this item blocks when equipped.
+ ///
+ public List SlotsToBlock
+ {
+ get
+ {
+ var result = new List();
+ if (S1ClothingDefinition.SlotsToBlock != null)
+ {
+ foreach (var slot in S1ClothingDefinition.SlotsToBlock)
+ {
+ result.Add((ClothingSlot)slot);
+ }
+ }
+ return result;
+ }
+ set
+ {
+#if (IL2CPPMELON)
+ S1ClothingDefinition.SlotsToBlock = new Il2CppCollections.List();
+#else
+ S1ClothingDefinition.SlotsToBlock = new List();
+#endif
+ if (value != null)
+ {
+ foreach (var slot in value)
+ {
+ S1ClothingDefinition.SlotsToBlock.Add((S1Clothing.EClothingSlot)slot);
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/S1API/Items/Clothing/ClothingItemInstance.cs b/S1API/Items/Clothing/ClothingItemInstance.cs
new file mode 100644
index 0000000..3efa0e4
--- /dev/null
+++ b/S1API/Items/Clothing/ClothingItemInstance.cs
@@ -0,0 +1,46 @@
+#if (IL2CPPMELON)
+using S1Clothing = Il2CppScheduleOne.Clothing;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1Clothing = ScheduleOne.Clothing;
+#endif
+using System;
+
+namespace S1API.Items.Clothing
+{
+ ///
+ /// Represents a clothing item instance in the game world (physical clothing you own).
+ /// Extends with color information.
+ ///
+ public class ClothingItemInstance : ItemInstance
+ {
+ ///
+ /// INTERNAL: Reference to the in-game clothing item instance.
+ ///
+ internal readonly S1Clothing.ClothingInstance S1ClothingInstance;
+
+ ///
+ /// INTERNAL: Creates a ClothingItemInstance wrapper.
+ ///
+ /// In-game clothing item instance
+ internal ClothingItemInstance(S1Clothing.ClothingInstance itemInstance)
+ : base(itemInstance)
+ {
+ S1ClothingInstance = itemInstance;
+ }
+
+ ///
+ /// The color of this clothing instance.
+ ///
+ public ClothingColor Color
+ {
+ get => (ClothingColor)S1ClothingInstance.Color;
+ set => S1ClothingInstance.Color = (S1Clothing.EClothingColor)value;
+ }
+
+ ///
+ /// The clothing definition (template) this instance was created from.
+ ///
+ public new ClothingItemDefinition Definition =>
+ new ClothingItemDefinition((S1Clothing.ClothingDefinition)S1ClothingInstance.Definition);
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/Clothing/ClothingSlot.cs b/S1API/Items/Clothing/ClothingSlot.cs
new file mode 100644
index 0000000..e8dcaf4
--- /dev/null
+++ b/S1API/Items/Clothing/ClothingSlot.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace S1API.Items.Clothing
+{
+ ///
+ /// Represents the slot where a clothing item can be equipped.
+ /// Mirrors ScheduleOne.Clothing.EClothingSlot.
+ ///
+ public enum ClothingSlot
+ {
+ /// Feet slot (shoes, boots).
+ Feet = 0,
+ /// Bottom slot (pants, shorts).
+ Bottom = 1,
+ /// Waist slot (belts).
+ Waist = 2,
+ /// Top slot (shirts).
+ Top = 3,
+ /// Outerwear slot (jackets, coats).
+ Outerwear = 4,
+ /// Hands slot (gloves).
+ Hands = 5,
+ /// Neck slot (necklaces, scarves).
+ Neck = 6,
+ /// Eyes slot (glasses, sunglasses).
+ Eyes = 7,
+ /// Head slot (hats, caps, helmets).
+ Head = 8,
+ /// Wrist slot (watches, bracelets).
+ Wrist = 9
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/ClothingApplicationType.cs b/S1API/Items/ClothingApplicationType.cs
index 44d4b01..22b7801 100644
--- a/S1API/Items/ClothingApplicationType.cs
+++ b/S1API/Items/ClothingApplicationType.cs
@@ -1,9 +1,12 @@
+using System;
+
namespace S1API.Items
{
///
/// Represents how a clothing item is applied to the avatar.
/// Mirrors ScheduleOne.Clothing.EClothingApplicationType.
///
+ [Obsolete]
public enum ClothingApplicationType
{
/// Applied as a body layer (flat texture on body mesh).
diff --git a/S1API/Items/ClothingColor.cs b/S1API/Items/ClothingColor.cs
index d57b8e3..a349391 100644
--- a/S1API/Items/ClothingColor.cs
+++ b/S1API/Items/ClothingColor.cs
@@ -1,9 +1,12 @@
+using System;
+
namespace S1API.Items
{
///
/// Represents available clothing colors.
/// Mirrors ScheduleOne.Clothing.EClothingColor.
///
+ [Obsolete]
public enum ClothingColor
{
White = 0,
diff --git a/S1API/Items/ClothingItemCreator.cs b/S1API/Items/ClothingItemCreator.cs
index 320a399..2bb4db4 100644
--- a/S1API/Items/ClothingItemCreator.cs
+++ b/S1API/Items/ClothingItemCreator.cs
@@ -6,6 +6,7 @@
using S1Clothing = ScheduleOne.Clothing;
#endif
+using System;
using S1API.Internal.Utils;
using S1API.Logging;
using UnityEngine;
@@ -16,6 +17,7 @@ namespace S1API.Items
/// Provides convenient static methods for creating custom clothing items.
/// Use for flexible configuration or for variants.
///
+ [Obsolete]
public static class ClothingItemCreator
{
private static readonly Log Logger = new Log("ClothingItemCreator");
diff --git a/S1API/Items/ClothingItemDefinition.cs b/S1API/Items/ClothingItemDefinition.cs
index b6b25d5..fbd07a8 100644
--- a/S1API/Items/ClothingItemDefinition.cs
+++ b/S1API/Items/ClothingItemDefinition.cs
@@ -6,6 +6,7 @@
using Il2CppCollections = System.Collections.Generic;
#endif
+using System;
using System.Collections.Generic;
namespace S1API.Items
@@ -18,6 +19,7 @@ namespace S1API.Items
/// Use to create new clothing items,
/// or to create variants of existing items.
///
+ [Obsolete]
public sealed class ClothingItemDefinition : StorableItemDefinition
{
///
diff --git a/S1API/Items/ClothingItemDefinitionBuilder.cs b/S1API/Items/ClothingItemDefinitionBuilder.cs
index 506bad1..ee93b7e 100644
--- a/S1API/Items/ClothingItemDefinitionBuilder.cs
+++ b/S1API/Items/ClothingItemDefinitionBuilder.cs
@@ -14,6 +14,7 @@
using Il2CppCollections = System.Collections.Generic;
#endif
+using System;
using System.Collections.Generic;
using S1API.Internal.Utils;
using S1API.Logging;
@@ -25,6 +26,7 @@ namespace S1API.Items
/// Builder for composing clothing item definitions at runtime.
/// Use fluent methods to configure clothing properties before calling .
///
+ [Obsolete]
public sealed class ClothingItemDefinitionBuilder
{
private static readonly Log Logger = new Log("ClothingItemDefinitionBuilder");
diff --git a/S1API/Items/ClothingItemInstance.cs b/S1API/Items/ClothingItemInstance.cs
index f545156..b59031e 100644
--- a/S1API/Items/ClothingItemInstance.cs
+++ b/S1API/Items/ClothingItemInstance.cs
@@ -3,6 +3,7 @@
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1Clothing = ScheduleOne.Clothing;
#endif
+using System;
namespace S1API.Items
{
@@ -10,6 +11,7 @@ namespace S1API.Items
/// Represents a clothing item instance in the game world (physical clothing you own).
/// Extends with color information.
///
+ [Obsolete]
public class ClothingItemInstance : ItemInstance
{
///
diff --git a/S1API/Items/ClothingSlot.cs b/S1API/Items/ClothingSlot.cs
index 4fdb3e9..7238a65 100644
--- a/S1API/Items/ClothingSlot.cs
+++ b/S1API/Items/ClothingSlot.cs
@@ -1,9 +1,12 @@
+using System;
+
namespace S1API.Items
{
///
/// Represents the slot where a clothing item can be equipped.
/// Mirrors ScheduleOne.Clothing.EClothingSlot.
///
+ [Obsolete]
public enum ClothingSlot
{
/// Feet slot (shoes, boots).
diff --git a/S1API/Items/ItemBuilders/AdditiveDefinitionBuilder.cs b/S1API/Items/ItemBuilders/AdditiveDefinitionBuilder.cs
new file mode 100644
index 0000000..a21db04
--- /dev/null
+++ b/S1API/Items/ItemBuilders/AdditiveDefinitionBuilder.cs
@@ -0,0 +1,141 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework;
+using S1Registry = Il2CppScheduleOne.Registry;
+using S1Storage = Il2CppScheduleOne.Storage;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+using S1CoreItemFramework = ScheduleOne.Core.Items.Framework;
+using S1Registry = ScheduleOne.Registry;
+using S1Storage = ScheduleOne.Storage;
+#endif
+
+using System;
+using S1API.Internal.Utils;
+using S1API.Logging;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+namespace S1API.Items.ItemBuilders
+{
+ ///
+ /// Builder for composing additive definitions at runtime.
+ /// Use fluent methods to configure additive properties before calling .
+ ///
+ public sealed class AdditiveDefinitionBuilder
+ : StorableItemDefinitionBuilder
+ {
+ private static readonly Log Logger = new Log("AdditiveDefinitionBuilder");
+
+ private S1ItemFramework.AdditiveDefinition AdditiveDefinition =>
+ CrossType.As(Definition);
+
+ ///
+ /// INTERNAL: Creates a new builder instance with a fresh AdditiveDefinition.
+ /// Only can instantiate this.
+ ///
+ internal AdditiveDefinitionBuilder()
+ : base(ScriptableObject.CreateInstance)
+ {
+ Definition.Category = S1CoreItemFramework.EItemCategory.Agriculture;
+ }
+
+ ///
+ /// INTERNAL: Creates a builder instance initialized by cloning an existing additive.
+ ///
+ internal AdditiveDefinitionBuilder(
+ S1ItemFramework.AdditiveDefinition source)
+ : base(source,
+ ScriptableObject.CreateInstance)
+ {
+ }
+
+ ///
+ protected override void CopyPropertiesFrom(
+ S1ItemFramework.StorableItemDefinition source)
+ {
+ base.CopyPropertiesFrom(source);
+
+ var additiveSource = CrossType.As(source);
+
+ // AdditiveDefinition properties (auto-properties with private set in Mono)
+ AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.DisplayMaterial),
+ additiveSource.DisplayMaterial);
+ AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.QualityChange),
+ additiveSource.QualityChange);
+ AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.YieldMultiplier),
+ additiveSource.YieldMultiplier);
+ AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.InstantGrowth),
+ additiveSource.InstantGrowth);
+ }
+
+ ///
+ /// Sets the display material for this additive.
+ ///
+ public AdditiveDefinitionBuilder WithDisplayMaterial(Material material)
+ {
+ if (!AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.DisplayMaterial),
+ material))
+ {
+ Logger.Warning(
+ $"Failed to set DisplayMaterial on AdditiveDefinition '{AdditiveDefinition.ID ?? ""}'.");
+ }
+
+ return this;
+ }
+
+ ///
+ /// Sets the effect values for this additive.
+ ///
+ public AdditiveDefinitionBuilder WithEffects(float yieldMultiplier, float instantGrowth, float qualityChange)
+ {
+ if (!AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.YieldMultiplier),
+ yieldMultiplier))
+ {
+ Logger.Warning(
+ $"Failed to set YieldMultiplier on AdditiveDefinition '{AdditiveDefinition.ID ?? ""}'.");
+ }
+
+ if (!AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.InstantGrowth),
+ instantGrowth))
+ {
+ Logger.Warning(
+ $"Failed to set InstantGrowth on AdditiveDefinition '{AdditiveDefinition.ID ?? ""}'.");
+ }
+
+ if (!AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.QualityChange),
+ qualityChange))
+ {
+ Logger.Warning(
+ $"Failed to set QualityChange on AdditiveDefinition '{AdditiveDefinition.ID ?? ""}'.");
+ }
+
+ return this;
+ }
+
+ ///
+ /// Builds the item definition, registers it with the game's registry, and returns a wrapper.
+ ///
+ /// A wrapper around the created additive definition.
+ public new Additive.AdditiveDefinition Build()
+ {
+ return (Additive.AdditiveDefinition)base.Build();
+ }
+
+ ///
+ /// INTERNAL: Builds and returns the raw game item definition without registering.
+ /// Used internally by S1API. Modders should use instead.
+ ///
+ internal new S1ItemFramework.AdditiveDefinition BuildInternal()
+ {
+ return AdditiveDefinition;
+ }
+
+ ///
+ protected override Storable.StorableItemDefinition CreateWrapper(
+ S1ItemFramework.StorableItemDefinition definition)
+ {
+ return new Additive.AdditiveDefinition(AdditiveDefinition);
+ }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/ItemBuilders/BuildableItemDefinitionBuilder.cs b/S1API/Items/ItemBuilders/BuildableItemDefinitionBuilder.cs
new file mode 100644
index 0000000..1d98c89
--- /dev/null
+++ b/S1API/Items/ItemBuilders/BuildableItemDefinitionBuilder.cs
@@ -0,0 +1,91 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+using S1CoreItemFramework = ScheduleOne.Core.Items.Framework;
+#endif
+
+using System;
+using S1API.Internal.Utils;
+using UnityEngine;
+
+namespace S1API.Items.ItemBuilders
+{
+ ///
+ /// Builder for composing buildable item definitions at runtime.
+ /// Use fluent methods to configure buildable item properties before calling .
+ ///
+ public class BuildableItemDefinitionBuilder
+ : StorableItemDefinitionBuilder
+ {
+ private S1ItemFramework.BuildableItemDefinition BuildableDefinition =>
+ CrossType.As(Definition);
+
+ ///
+ /// INTERNAL: Creates a new builder instance with a fresh BuildableItemDefinition.
+ /// Only BuildableItemCreator can instantiate this.
+ ///
+ internal BuildableItemDefinitionBuilder()
+ : base(ScriptableObject.CreateInstance)
+ {
+ Definition.Category = S1CoreItemFramework.EItemCategory.Furniture;
+ BuildableDefinition.BuildSoundType =
+ S1ItemFramework.BuildableItemDefinition.EBuildSoundType.Wood;
+ }
+
+ ///
+ /// INTERNAL: Creates a builder instance initialized by cloning an existing item.
+ ///
+ internal BuildableItemDefinitionBuilder(S1ItemFramework.BuildableItemDefinition source)
+ : base(source, ScriptableObject.CreateInstance)
+ {
+ }
+
+ ///
+ protected override void CopyPropertiesFrom(S1ItemFramework.StorableItemDefinition source)
+ {
+ base.CopyPropertiesFrom(source);
+ var buildableSource = CrossType.As(source);
+
+ BuildableDefinition.BuildSoundType = buildableSource.BuildSoundType;
+ BuildableDefinition.BuiltItem = buildableSource.BuiltItem;
+ }
+
+ ///
+ /// Sets the sound type played when this item is built.
+ ///
+ /// The build sound type.
+ /// The builder instance for fluent chaining.
+ public BuildableItemDefinitionBuilder WithBuildSound(BuildSoundType soundType)
+ {
+ BuildableDefinition.BuildSoundType = (S1ItemFramework.BuildableItemDefinition.EBuildSoundType)soundType;
+ return this;
+ }
+
+ ///
+ /// Builds the item definition, registers it with the game's registry, and returns a wrapper.
+ ///
+ /// A wrapper around the created buildable item definition.
+ public new Buildable.BuildableItemDefinition Build()
+ {
+ return (Buildable.BuildableItemDefinition)base.Build();
+ }
+
+ ///
+ /// INTERNAL: Builds and returns the raw game item definition without registering.
+ /// Used internally by S1API. Modders should use instead.
+ ///
+ internal new S1ItemFramework.BuildableItemDefinition BuildInternal()
+ {
+ return BuildableDefinition;
+ }
+
+ ///
+ protected override Storable.StorableItemDefinition CreateWrapper(
+ S1ItemFramework.StorableItemDefinition definition)
+ {
+ return new Buildable.BuildableItemDefinition(BuildableDefinition);
+ }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/ItemBuilders/ClothingItemDefinitionBuilder.cs b/S1API/Items/ItemBuilders/ClothingItemDefinitionBuilder.cs
new file mode 100644
index 0000000..9c60d81
--- /dev/null
+++ b/S1API/Items/ItemBuilders/ClothingItemDefinitionBuilder.cs
@@ -0,0 +1,276 @@
+#if (IL2CPPMELON)
+using S1Clothing = Il2CppScheduleOne.Clothing;
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework;
+using S1Registry = Il2CppScheduleOne.Registry;
+using S1UiItems = Il2CppScheduleOne.UI.Items;
+using Il2CppCollections = Il2CppSystem.Collections.Generic;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1Clothing = ScheduleOne.Clothing;
+using S1ItemFramework = ScheduleOne.ItemFramework;
+using S1CoreItemFramework = ScheduleOne.Core.Items.Framework;
+using S1Registry = ScheduleOne.Registry;
+using S1UiItems = ScheduleOne.UI.Items;
+using Il2CppCollections = System.Collections.Generic;
+#endif
+
+using System;
+using System.Collections.Generic;
+using S1API.Internal.Utils;
+using S1API.Logging;
+using UnityEngine;
+
+namespace S1API.Items.ItemBuilders
+{
+ ///
+ /// Builder for composing clothing item definitions at runtime.
+ /// Use fluent methods to configure clothing properties before calling .
+ ///
+ public class ClothingItemDefinitionBuilder
+ : StorableItemDefinitionBuilder
+ {
+ private S1Clothing.ClothingDefinition ClothingDefinition =>
+ CrossType.As(Definition);
+
+ private static readonly Log Logger = new Log("ClothingItemDefinitionBuilder");
+ private static readonly HashSet WarnedMissingNativeClothingItemUiReasons = new HashSet();
+ private static readonly object WarnedMissingNativeClothingItemUiLock = new object();
+ private static S1UiItems.ItemUI? s_cachedNativeCustomItemUI;
+
+ ///
+ /// INTERNAL: Creates a new builder instance with a fresh ClothingDefinition.
+ ///
+ internal ClothingItemDefinitionBuilder()
+ : base(ScriptableObject.CreateInstance())
+ {
+ Definition.Category = S1CoreItemFramework.EItemCategory.Clothing;
+
+ // Clothing-specific defaults
+ ClothingDefinition.Slot = S1Clothing.EClothingSlot.Head;
+ ClothingDefinition.ApplicationType = S1Clothing.EClothingApplicationType.Accessory;
+ ClothingDefinition.ClothingAssetPath = "Path/To/Clothing/Asset";
+ ClothingDefinition.Colorable = true;
+ ClothingDefinition.DefaultColor = S1Clothing.EClothingColor.White;
+#if (IL2CPPMELON)
+ ClothingDefinition.SlotsToBlock = new Il2CppCollections.List();
+#else
+ ClothingDefinition.SlotsToBlock = new List();
+#endif
+ }
+
+ ///
+ /// INTERNAL: Creates a builder from an existing clothing definition (for cloning).
+ ///
+ internal ClothingItemDefinitionBuilder(S1Clothing.ClothingDefinition source)
+ : base(source, ScriptableObject.CreateInstance)
+ {
+ }
+
+ ///
+ protected override void CopyPropertiesFrom(
+ S1ItemFramework.StorableItemDefinition source)
+ {
+ base.CopyPropertiesFrom(source);
+
+ var clothingSource = CrossType.As(source);
+
+ ClothingDefinition.Slot = clothingSource.Slot;
+ ClothingDefinition.ApplicationType = clothingSource.ApplicationType;
+ ClothingDefinition.ClothingAssetPath = clothingSource.ClothingAssetPath;
+ ClothingDefinition.Colorable = clothingSource.Colorable;
+ ClothingDefinition.DefaultColor = clothingSource.DefaultColor;
+#if (IL2CPPMELON)
+ ClothingDefinition.SlotsToBlock = new Il2CppCollections.List();
+ if (clothingSource.SlotsToBlock != null)
+ {
+ foreach (var slot in clothingSource.SlotsToBlock)
+ {
+ ClothingDefinition.SlotsToBlock.Add(slot);
+ }
+ }
+#else
+ ClothingDefinition.SlotsToBlock = clothingSource.SlotsToBlock == null
+ ? new List()
+ : new List(clothingSource.SlotsToBlock);
+#endif
+ }
+
+ ///
+ /// Sets the clothing slot this item occupies.
+ ///
+ /// The clothing slot.
+ /// The builder instance for fluent chaining.
+ public ClothingItemDefinitionBuilder WithSlot(Clothing.ClothingSlot slot)
+ {
+ ClothingDefinition.Slot = (S1Clothing.EClothingSlot)slot;
+ return this;
+ }
+
+ ///
+ /// Sets how this clothing item is applied to the avatar.
+ ///
+ /// The application type.
+ /// The builder instance for fluent chaining.
+ public ClothingItemDefinitionBuilder WithApplicationType(Clothing.ClothingApplicationType applicationType)
+ {
+ ClothingDefinition.ApplicationType = (S1Clothing.EClothingApplicationType)applicationType;
+ return this;
+ }
+
+ ///
+ /// Sets the asset path to the clothing prefab or layer.
+ ///
+ /// Resources path to the clothing asset.
+ /// The builder instance for fluent chaining.
+ public ClothingItemDefinitionBuilder WithClothingAsset(string assetPath)
+ {
+ ClothingDefinition.ClothingAssetPath = assetPath;
+ return this;
+ }
+
+ ///
+ /// Sets whether this clothing item can be colored.
+ ///
+ /// True if colorable, false otherwise.
+ /// The builder instance for fluent chaining.
+ public ClothingItemDefinitionBuilder WithColorable(bool colorable)
+ {
+ ClothingDefinition.Colorable = colorable;
+ return this;
+ }
+
+ ///
+ /// Sets the default color for this clothing item.
+ ///
+ /// The default color.
+ /// The builder instance for fluent chaining.
+ public ClothingItemDefinitionBuilder WithDefaultColor(Clothing.ClothingColor color)
+ {
+ ClothingDefinition.DefaultColor = (S1Clothing.EClothingColor)color;
+ return this;
+ }
+
+ ///
+ /// Sets the list of clothing slots this item blocks when equipped.
+ ///
+ /// Array of slots to block.
+ /// The builder instance for fluent chaining.
+ public ClothingItemDefinitionBuilder WithBlockedSlots(params Clothing.ClothingColor[] slots)
+ {
+#if (IL2CPPMELON)
+ ClothingDefinition.SlotsToBlock = new Il2CppCollections.List();
+ foreach (var slot in slots)
+ {
+ ClothingDefinition.SlotsToBlock.Add((S1Clothing.EClothingSlot)slot);
+ }
+#else
+ ClothingDefinition.SlotsToBlock = new List();
+ foreach (var slot in slots)
+ {
+ ClothingDefinition.SlotsToBlock.Add((S1Clothing.EClothingSlot)slot);
+ }
+#endif
+ return this;
+ }
+
+ ///
+ /// Builds the item definition, registers it with the game's registry, and returns a wrapper.
+ ///
+ /// A wrapper around the created clothing item definition.
+ public new Clothing.ClothingItemDefinition Build()
+ {
+ EnsureNativeClothingItemUi();
+ return (Clothing.ClothingItemDefinition)base.Build();
+ }
+
+ ///
+ /// INTERNAL: Builds and returns the raw game item definition without registering.
+ /// Used internally by S1API. Modders should use instead.
+ ///
+ internal new S1Clothing.ClothingDefinition BuildInternal()
+ {
+ EnsureNativeClothingItemUi();
+ return ClothingDefinition;
+ }
+
+ ///
+ protected override Storable.StorableItemDefinition CreateWrapper(
+ S1ItemFramework.StorableItemDefinition definition)
+ {
+ return new Clothing.ClothingItemDefinition(ClothingDefinition);
+ }
+
+
+ private void EnsureNativeClothingItemUi()
+ {
+ if (ClothingDefinition.CustomItemUI != null)
+ {
+ return;
+ }
+
+ if (s_cachedNativeCustomItemUI != null)
+ {
+ ClothingDefinition.CustomItemUI = s_cachedNativeCustomItemUI;
+ return;
+ }
+
+ if (S1Registry.Instance == null)
+ {
+ WarnMissingNativeClothingItemUi("S1Registry.Instance is null");
+ return;
+ }
+
+ var allItems = S1Registry.Instance.GetAllItems();
+ if (allItems == null)
+ {
+ WarnMissingNativeClothingItemUi("S1Registry.Instance.GetAllItems() returned null");
+ return;
+ }
+
+ foreach (var item in allItems)
+ {
+ if (item == null ||
+ !CrossType.Is(item, out S1Clothing.ClothingDefinition clothingDefinition))
+ {
+ continue;
+ }
+
+ var customItemUI = clothingDefinition.CustomItemUI;
+ if (customItemUI == null)
+ {
+ continue;
+ }
+
+ // CustomItemUI is a native UI template. Share the existing template instead of
+ // cloning it here; listing state is bound per item by the game, and cloning
+ // Unity/Il2Cpp UI objects is riskier across runtimes.
+ s_cachedNativeCustomItemUI = customItemUI;
+ ClothingDefinition.CustomItemUI = customItemUI;
+ return;
+ }
+
+ WarnMissingNativeClothingItemUi(
+ "no S1Clothing.ClothingDefinition with S1Clothing.ClothingDefinition.CustomItemUI was found");
+ }
+
+ private static void WarnMissingNativeClothingItemUi(string reason)
+ {
+ if (string.IsNullOrWhiteSpace(reason))
+ {
+ return;
+ }
+
+ bool shouldWarn;
+ lock (WarnedMissingNativeClothingItemUiLock)
+ {
+ shouldWarn = WarnedMissingNativeClothingItemUiReasons.Add(reason);
+ }
+
+ if (shouldWarn)
+ {
+ Logger.Warning(
+ $"Could not borrow a native clothing CustomItemUI template ({reason}). Custom clothing inventory UI may be incomplete. This usually means Build() was called before any native clothing registered.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs b/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs
index 5868188..8a448c1 100644
--- a/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs
+++ b/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs
@@ -59,7 +59,7 @@ protected override void CopyPropertiesFrom(
///
/// The default quality to assign to items of this definition.
/// >The builder instance for fluent chaining.
- public QualityItemDefinitionBuilder WithDefaultQuality(Quality quality)
+ public QualityItemDefinitionBuilder WithDefaultQuality(Products.Quality quality)
{
QualityDefinition.DefaultQuality = (S1ItemFramework.EQuality)quality;
return this;
@@ -69,9 +69,9 @@ public QualityItemDefinitionBuilder WithDefaultQuality(Quality quality)
/// Builds the item definition, registers it with the game's registry, and returns a wrapper.
///
/// A wrapper around the created quality item definition.
- public new QualityItemDefinition Build()
+ public new Quality.QualityItemDefinition Build()
{
- return (QualityItemDefinition)base.Build();
+ return (Quality.QualityItemDefinition)base.Build();
}
///
@@ -84,10 +84,10 @@ public QualityItemDefinitionBuilder WithDefaultQuality(Quality quality)
}
///
- protected override StorableItemDefinition CreateWrapper(
+ protected override Storable.StorableItemDefinition CreateWrapper(
S1ItemFramework.StorableItemDefinition definition)
{
- return new QualityItemDefinition(QualityDefinition);
+ return new Quality.QualityItemDefinition(QualityDefinition);
}
}
}
\ No newline at end of file
diff --git a/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs b/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs
index 556f44b..570fe5e 100644
--- a/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs
+++ b/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs
@@ -49,7 +49,7 @@ internal StorableItemDefinitionBuilder()
/// Builds the item definition, registers it with the game's registry, and returns a wrapper.
///
/// A wrapper around the created storable item definition.
- public new StorableItemDefinition Build()
+ public new Storable.StorableItemDefinition Build()
{
return base.Build();
}
@@ -365,7 +365,7 @@ public TSelf WithRequiredRank(Leveling.FullRank? rank)
/// Builds the item definition, registers it with the game's registry, and returns a wrapper.
///
/// A wrapper around the created storable item definition.
- public virtual StorableItemDefinition Build()
+ public virtual Storable.StorableItemDefinition Build()
{
if (!_hasCustomStoredItem && Definition.StoredItem != null)
{
@@ -398,10 +398,10 @@ internal virtual S1ItemFramework.StorableItemDefinition BuildInternal()
///
/// The item definition to wrap.
/// >A wrapper around the given item definition.
- protected virtual StorableItemDefinition CreateWrapper(
+ protected virtual Storable.StorableItemDefinition CreateWrapper(
S1ItemFramework.StorableItemDefinition definition)
{
- return new StorableItemDefinition(definition);
+ return new Storable.StorableItemDefinition(definition);
}
private static S1StationFramework.StationItem GetOrCreateStationItemPrefab(
diff --git a/S1API/Items/ItemCreator.cs b/S1API/Items/ItemCreator.cs
index b447be6..b8b171f 100644
--- a/S1API/Items/ItemCreator.cs
+++ b/S1API/Items/ItemCreator.cs
@@ -19,6 +19,7 @@ namespace S1API.Items
///
/// All items in Schedule One are storable items (StorableItemDefinition), so both methods create the same type.
///
+ [Obsolete("Use S1API.Items.Storable.ItemCreator instead")]
public static class ItemCreator
{
///
diff --git a/S1API/Items/ItemManager.cs b/S1API/Items/ItemManager.cs
index d28260b..44e2429 100644
--- a/S1API/Items/ItemManager.cs
+++ b/S1API/Items/ItemManager.cs
@@ -31,6 +31,7 @@ public static class ItemManager
///
/// The ID of the item.
/// An instance of the item definition.
+ [Obsolete("Use S1API.Items.ItemManager.GetDefinition instead.")]
public static ItemDefinition GetItemDefinition(string itemID)
{
S1ItemFramework.ItemDefinition itemDefinition = S1Registry.GetItem(itemID);
@@ -74,6 +75,54 @@ public static ItemDefinition GetItemDefinition(string itemID)
return new ItemDefinition(itemDefinition);
}
+ ///
+ /// Gets the definition of an item by its ID.
+ ///
+ /// The ID of the item.
+ /// An instance of the item definition.
+ public static ItemDefinition? GetDefinition(string itemID)
+ {
+ var itemDefinition = S1Registry.GetItem(itemID);
+
+ if (itemDefinition == null)
+ return null;
+
+ // Check for specific types first (most derived to least derived)
+ if (CrossType.Is(itemDefinition,
+ out S1Product.ProductDefinition productDefinition))
+ return new ProductDefinition(productDefinition);
+
+ if (CrossType.Is(itemDefinition,
+ out S1ItemFramework.CashDefinition cashDefinition))
+ return new CashDefinition(cashDefinition);
+
+ if (CrossType.Is(itemDefinition,
+ out S1Clothing.ClothingDefinition clothingDefinition))
+ return new Clothing.ClothingItemDefinition(clothingDefinition);
+
+ if (CrossType.Is(itemDefinition,
+ out S1ItemFramework.BuildableItemDefinition buildableItemDefinition))
+ return new Buildable.BuildableItemDefinition(buildableItemDefinition);
+
+ if (CrossType.Is(itemDefinition,
+ out S1Packaging.PackagingDefinition packagingDefinition))
+ return new PackagingDefinition(packagingDefinition);
+
+ if (CrossType.Is(itemDefinition,
+ out S1ItemFramework.AdditiveDefinition additiveDefinition))
+ return new Additive.AdditiveDefinition(additiveDefinition);
+
+ if (CrossType.Is(itemDefinition,
+ out S1ItemFramework.QualityItemDefinition qualityItemDefinition))
+ return new Quality.QualityItemDefinition(qualityItemDefinition);
+
+ if (CrossType.Is(itemDefinition,
+ out S1ItemFramework.StorableItemDefinition storableItemDefinition))
+ return new Storable.StorableItemDefinition(storableItemDefinition);
+
+ return new ItemDefinition(itemDefinition);
+ }
+
///
/// Manually registers an item definition with the game's registry.
/// This is typically handled automatically by methods,
diff --git a/S1API/Items/Quality/QualityItemCreator.cs b/S1API/Items/Quality/QualityItemCreator.cs
new file mode 100644
index 0000000..470aa0a
--- /dev/null
+++ b/S1API/Items/Quality/QualityItemCreator.cs
@@ -0,0 +1,67 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+using S1Registry = Il2CppScheduleOne.Registry;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+using S1Registry = ScheduleOne.Registry;
+#endif
+using System;
+using S1API.Internal.Utils;
+
+namespace S1API.Items.Quality
+{
+ ///
+ /// Provides convenient static methods for creating custom quality items.
+ /// Use for flexible configuration
+ /// or for quick variants based on existing items.
+ ///
+ public class QualityItemCreator
+ {
+ ///
+ /// Creates a new builder for composing a quality item definition with full flexibility.
+ /// Use fluent methods to configure the definition, then call Build() to register it.
+ ///
+ public static ItemBuilders.QualityItemDefinitionBuilder CreateBuilder()
+ {
+ return new ItemBuilders.QualityItemDefinitionBuilder();
+ }
+
+ ///
+ /// Creates a new quality item builder by cloning an existing quality item by ID.
+ ///
+ /// The ID of the item to clone.
+ /// A builder pre-configured with the source item properties.
+ /// Thrown if the source item ID is not found or is not a quality item.
+ public static ItemBuilders.QualityItemDefinitionBuilder CloneFrom(string sourceItemId)
+ {
+ var sourceDefinition = S1Registry.GetItem(sourceItemId);
+ if (sourceDefinition == null)
+ {
+ throw new ArgumentException($"Source item with ID '{sourceItemId}' not found in registry", nameof(sourceItemId));
+ }
+
+ if (!CrossType.Is(sourceDefinition, out S1ItemFramework.QualityItemDefinition qualityDef))
+ {
+ throw new ArgumentException($"Item '{sourceItemId}' is not an QualityItemDefinition", nameof(sourceItemId));
+ }
+
+ return new ItemBuilders.QualityItemDefinitionBuilder(qualityDef);
+ }
+
+ ///
+ /// Creates a new quality item builder by cloning an existing quality item wrapper.
+ ///
+ /// The quality item definition to clone.
+ /// A builder pre-configured with the source item properties.
+ /// Thrown if the source definition is null.
+ public static ItemBuilders.QualityItemDefinitionBuilder CloneFrom(QualityItemDefinition source)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source), "Source storable item definition cannot be null");
+ }
+
+ return new ItemBuilders.QualityItemDefinitionBuilder(source.S1QualityDefinition);
+ }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/Quality/QualityItemDefinition.cs b/S1API/Items/Quality/QualityItemDefinition.cs
new file mode 100644
index 0000000..3ae4aaa
--- /dev/null
+++ b/S1API/Items/Quality/QualityItemDefinition.cs
@@ -0,0 +1,66 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+#endif
+
+namespace S1API.Items.Quality
+{
+ ///
+ /// Represents a quality item definition that can be consumed or used in recipes
+ /// Extends with quality-specific properties.
+ ///
+ ///
+ /// Use
+ ///
+ public class QualityItemDefinition : Storable.StorableItemDefinition
+ {
+ ///
+ /// INTERNAL: Wraps an existing native quality item definition.
+ ///
+ internal QualityItemDefinition(S1ItemFramework.QualityItemDefinition definition) : base(definition)
+ {
+ S1QualityDefinition = definition;
+ }
+
+ ///
+ /// INTERNAL: The underlying S1 quality item definition instance.
+ ///
+ internal S1ItemFramework.QualityItemDefinition S1QualityDefinition { get; }
+
+ ///
+ /// Creates a quality item instance from this definition using the default quality.
+ ///
+ /// The quantity to apply to the created instance.
+ /// A quality item instance using this definition's default quality.
+ public override ItemInstance CreateInstance(int quantity = 1) => CreateInstance(quantity, DefaultQuality);
+
+ ///
+ /// Creates a quality item instance from this definition with the specified quality.
+ ///
+ /// The quality to apply to the created instance.
+ /// A quality item instance using the specified quality.
+ public QualityItemInstance CreateInstance(Products.Quality quality) => CreateInstance(1, quality);
+
+ ///
+ /// Creates a quality item instance from this definition with the specified quantity and quality.
+ ///
+ /// The quantity to apply to the created instance.
+ /// The quality to apply to the created instance.
+ /// A quality item instance using the specified quantity and quality.
+ public QualityItemInstance CreateInstance(int quantity, Products.Quality quality) =>
+ new QualityItemInstance(new S1ItemFramework.QualityItemInstance(
+ S1QualityDefinition,
+ quantity,
+ (S1ItemFramework.EQuality)quality));
+
+ ///
+ /// The default quality for this item.
+ ///
+ public Products.Quality DefaultQuality
+ {
+ get => (Products.Quality)S1QualityDefinition.DefaultQuality;
+ set => S1QualityDefinition.DefaultQuality = (S1ItemFramework.EQuality)value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/Quality/QualityItemInstance.cs b/S1API/Items/Quality/QualityItemInstance.cs
new file mode 100644
index 0000000..c3e705c
--- /dev/null
+++ b/S1API/Items/Quality/QualityItemInstance.cs
@@ -0,0 +1,44 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+#endif
+
+namespace S1API.Items.Quality
+{
+ ///
+ /// Represents a quality item instance in the game world (usable item).
+ /// Extends with quality information.
+ ///
+ public class QualityItemInstance : ItemInstance
+ {
+ ///
+ /// INTERNAL: Reference to the in-game quality item instance.
+ ///
+ internal readonly S1ItemFramework.QualityItemInstance S1QualityInstance;
+
+ ///
+ /// INTERNAL: Creates a QualityItemInstance wrapper.
+ ///
+ /// In-game quality item instance
+ internal QualityItemInstance(S1ItemFramework.QualityItemInstance itemInstance) : base(itemInstance)
+ {
+ S1QualityInstance = itemInstance;
+ }
+
+ ///
+ /// The quality of this item.
+ ///
+ public Products.Quality Quality
+ {
+ get => (Products.Quality)S1QualityInstance.Quality;
+ set => S1QualityInstance.Quality = (S1ItemFramework.EQuality)value;
+ }
+
+ ///
+ /// The quality item definition (template) this instance was created from.
+ ///
+ public new QualityItemDefinition Definition =>
+ new QualityItemDefinition((S1ItemFramework.QualityItemDefinition)S1QualityInstance.Definition);
+ }
+}
\ No newline at end of file
diff --git a/S1API/Items/QualityItemCreator.cs b/S1API/Items/QualityItemCreator.cs
index 3255b15..67ef874 100644
--- a/S1API/Items/QualityItemCreator.cs
+++ b/S1API/Items/QualityItemCreator.cs
@@ -15,6 +15,7 @@ namespace S1API.Items
/// Use for flexible configuration
/// or for quick variants based on existing items.
///
+ [Obsolete("Use S1API.Items.Quality.QualityItemCreator instead")]
public class QualityItemCreator
{
///
diff --git a/S1API/Items/QualityItemDefinition.cs b/S1API/Items/QualityItemDefinition.cs
index 576474c..68ad6f3 100644
--- a/S1API/Items/QualityItemDefinition.cs
+++ b/S1API/Items/QualityItemDefinition.cs
@@ -3,6 +3,7 @@
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
+using System;
using S1API.Products;
namespace S1API.Items
@@ -14,6 +15,7 @@ namespace S1API.Items
///
/// Use
///
+ [Obsolete("Use S1API.Items.Quality.QualityItemDefinition instead")]
public class QualityItemDefinition : StorableItemDefinition
{
///
@@ -41,7 +43,7 @@ internal QualityItemDefinition(S1ItemFramework.QualityItemDefinition definition)
///
/// The quality to apply to the created instance.
/// A quality item instance using the specified quality.
- public QualityItemInstance CreateInstance(Quality quality) => CreateInstance(1, quality);
+ public QualityItemInstance CreateInstance(Products.Quality quality) => CreateInstance(1, quality);
///
/// Creates a quality item instance from this definition with the specified quantity and quality.
@@ -49,7 +51,7 @@ internal QualityItemDefinition(S1ItemFramework.QualityItemDefinition definition)
/// The quantity to apply to the created instance.
/// The quality to apply to the created instance.
/// A quality item instance using the specified quantity and quality.
- public QualityItemInstance CreateInstance(int quantity, Quality quality) =>
+ public QualityItemInstance CreateInstance(int quantity, Products.Quality quality) =>
new QualityItemInstance(new S1ItemFramework.QualityItemInstance(
S1QualityDefinition,
quantity,
@@ -58,9 +60,9 @@ public QualityItemInstance CreateInstance(int quantity, Quality quality) =>
///
/// The default quality for this item.
///
- public Quality DefaultQuality
+ public Products.Quality DefaultQuality
{
- get => (Quality)S1QualityDefinition.DefaultQuality;
+ get => (Products.Quality)S1QualityDefinition.DefaultQuality;
set => S1QualityDefinition.DefaultQuality = (S1ItemFramework.EQuality)value;
}
}
diff --git a/S1API/Items/QualityItemDefinitionBuilder.cs b/S1API/Items/QualityItemDefinitionBuilder.cs
index 1255313..a02c21e 100644
--- a/S1API/Items/QualityItemDefinitionBuilder.cs
+++ b/S1API/Items/QualityItemDefinitionBuilder.cs
@@ -27,7 +27,7 @@ namespace S1API.Items
/// Builder for composing quality item definitions at runtime.
/// Use fluent methods to configure item properties before calling
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.ItemBuilders.QualityItemDefinitionBuilder instead")]
public sealed class QualityItemDefinitionBuilder
{
private static readonly Log Logger = new Log("QualityItemDefinitionBuilder");
@@ -263,7 +263,7 @@ public QualityItemDefinitionBuilder WithRequiredRank(Leveling.FullRank? rank)
///
/// The default quality to assign to items of this definition.
/// >The builder instance for fluent chaining.
- public QualityItemDefinitionBuilder WithDefaultQuality(Quality quality)
+ public QualityItemDefinitionBuilder WithDefaultQuality(Products.Quality quality)
{
_definition.DefaultQuality = (S1ItemFramework.EQuality)quality;
return this;
diff --git a/S1API/Items/QualityItemInstance.cs b/S1API/Items/QualityItemInstance.cs
index b78ec11..4cde406 100644
--- a/S1API/Items/QualityItemInstance.cs
+++ b/S1API/Items/QualityItemInstance.cs
@@ -3,6 +3,7 @@
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
+using System;
using S1API.Products;
namespace S1API.Items
@@ -11,6 +12,7 @@ namespace S1API.Items
/// Represents a quality item instance in the game world (usable item).
/// Extends with quality information.
///
+ [Obsolete("Use S1API.Items.Quality.QualityItemInstance instead")]
public class QualityItemInstance : ItemInstance
{
///
@@ -30,9 +32,9 @@ internal QualityItemInstance(S1ItemFramework.QualityItemInstance itemInstance) :
///
/// The quality of this item.
///
- public Quality Quality
+ public Products.Quality Quality
{
- get => (Quality)S1QualityInstance.Quality;
+ get => (Products.Quality)S1QualityInstance.Quality;
set => S1QualityInstance.Quality = (S1ItemFramework.EQuality)value;
}
diff --git a/S1API/Items/Storable/ItemCreator.cs b/S1API/Items/Storable/ItemCreator.cs
new file mode 100644
index 0000000..31b7e70
--- /dev/null
+++ b/S1API/Items/Storable/ItemCreator.cs
@@ -0,0 +1,163 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+using S1Registry = Il2CppScheduleOne.Registry;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+using S1Registry = ScheduleOne.Registry;
+#endif
+using System;
+using S1API.Internal.Utils;
+using S1API.Leveling;
+using UnityEngine;
+
+namespace S1API.Items.Storable
+{
+ ///
+ /// Provides convenient static methods for creating custom items.
+ /// Use for flexible configuration or for quick creation.
+ ///
+ ///
+ /// All items in Schedule One are storable items (StorableItemDefinition), so both methods create the same type.
+ ///
+ public static class ItemCreator
+ {
+ ///
+ /// Creates a new builder for composing an item definition with full flexibility.
+ /// Use fluent methods to configure the item, then call Build() to register it.
+ ///
+ /// A new StorableItemDefinitionBuilder instance for fluent configuration.
+ ///
+ ///
+ /// var item = ItemCreator.CreateBuilder()
+ /// .WithBasicInfo("my_tool", "Custom Tool", "A custom tool", ItemCategory.Tools)
+ /// .WithStackLimit(5)
+ /// .WithPricing(25f, 0.3f)
+ /// .Build();
+ ///
+ ///
+ public static ItemBuilders.StorableItemDefinitionBuilder CreateBuilder()
+ {
+ return new ItemBuilders.StorableItemDefinitionBuilder();
+ }
+
+ ///
+ /// Creates a new storable item builder by cloning an existing item by ID.
+ ///
+ /// The ID of the item to clone.
+ /// A builder pre-configured with the source item properties.
+ /// Thrown if the source item ID is not found or is not a storable item.
+ public static ItemBuilders.StorableItemDefinitionBuilder CloneFrom(string sourceItemId)
+ {
+ var sourceDefinition = S1Registry.GetItem(sourceItemId);
+ if (sourceDefinition == null)
+ {
+ throw new ArgumentException($"Source item with ID '{sourceItemId}' not found in registry", nameof(sourceItemId));
+ }
+
+ if (!CrossType.Is(sourceDefinition, out S1ItemFramework.StorableItemDefinition storableDef))
+ {
+ throw new ArgumentException($"Item '{sourceItemId}' is not an StorableItemDefinition", nameof(sourceItemId));
+ }
+
+ return new ItemBuilders.StorableItemDefinitionBuilder(storableDef);
+ }
+
+ ///
+ /// Creates a new storable item builder by cloning an existing storable item wrapper.
+ ///
+ /// The storable item definition to clone.
+ /// A builder pre-configured with the source item properties.
+ /// Thrown if the source definition is null.
+ public static ItemBuilders.StorableItemDefinitionBuilder CloneFrom(StorableItemDefinition source)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source), "Source storable item definition cannot be null");
+ }
+
+ return new ItemBuilders.StorableItemDefinitionBuilder(source.S1StorableItemDefinition);
+ }
+
+ ///
+ /// Creates an item with common parameters in a single call.
+ /// The item is automatically registered with the game's registry.
+ ///
+ /// Unique identifier for the item (e.g., "my_custom_tool").
+ /// Display name shown in UI.
+ /// Item description shown in tooltips.
+ /// Item category for inventory organization.
+ /// Maximum quantity per inventory slot (default: 10).
+ /// Base price when buying from shops (default: 10).
+ /// Fraction of purchase price recovered when selling (default: 0.5).
+ /// Whether the item is legal or illegal (default: Legal).
+ /// Whether purchasing the item requires a certain player rank (default: false).
+ /// The player rank required to purchase the item, if applicable (default: null).
+ /// Optional sprite to use as the item icon.
+ /// Optional equippable component to attach.
+ /// A wrapper around the created item definition.
+ ///
+ ///
+ /// var item = ItemCreator.CreateItem(
+ /// id: "my_tool",
+ /// name: "Custom Tool",
+ /// description: "A custom tool for crafting",
+ /// category: ItemCategory.Tools,
+ /// stackLimit: 5,
+ /// basePurchasePrice: 25f
+ /// );
+ ///
+ ///
+ public static StorableItemDefinition CreateItem(
+ string id,
+ string name,
+ string description,
+ ItemCategory category,
+ int stackLimit = 10,
+ float basePurchasePrice = 10f,
+ float resellMultiplier = 0.5f,
+ LegalStatus legalStatus = LegalStatus.Legal,
+ bool requiresLevelToPurchase = false,
+ FullRank? requiredRank = null,
+ Sprite icon = null,
+ Equippable equippable = null)
+ {
+ var builder = new ItemBuilders.StorableItemDefinitionBuilder()
+ .WithBasicInfo(id, name, description, category)
+ .WithStackLimit(stackLimit)
+ .WithPricing(basePurchasePrice, resellMultiplier)
+ .WithRequiredRank(requiredRank)
+ .WithLegalStatus(legalStatus);
+
+ if (icon != null)
+ {
+ builder.WithIcon(icon);
+ }
+
+ if (equippable != null)
+ {
+ builder.WithEquippable(equippable);
+ }
+
+ return builder.Build();
+ }
+
+ ///
+ /// Creates a new equippable builder for creating custom equippable components.
+ /// Use this to create equippable behavior that can be attached to items.
+ ///
+ /// A new EquippableBuilder instance.
+ ///
+ ///
+ /// var equippable = ItemCreator.CreateEquippableBuilder()
+ /// .CreateBasicEquippable("MyEquippable")
+ /// .WithInteraction(canInteract: true, canPickup: true)
+ /// .Build();
+ ///
+ ///
+ public static EquippableBuilder CreateEquippableBuilder()
+ {
+ return new EquippableBuilder();
+ }
+ }
+}
+
diff --git a/S1API/Items/Storable/StorableItemDefinition.cs b/S1API/Items/Storable/StorableItemDefinition.cs
new file mode 100644
index 0000000..e917c94
--- /dev/null
+++ b/S1API/Items/Storable/StorableItemDefinition.cs
@@ -0,0 +1,94 @@
+#if (IL2CPPMELON)
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1ItemFramework = ScheduleOne.ItemFramework;
+#endif
+using S1API.Leveling;
+using UnityEngine;
+
+namespace S1API.Items.Storable
+{
+ ///
+ /// Represents an item definition that can be purchased, sold, and stored in inventories.
+ /// Extends with economic properties.
+ ///
+ ///
+ /// In Schedule One, all items are StorableItemDefinition or subclasses thereof.
+ /// The base ItemDefinition class is abstract and not used directly in gameplay.
+ ///
+ public class StorableItemDefinition : ItemDefinition
+ {
+ ///
+ /// INTERNAL: Wraps an existing native storable item definition.
+ ///
+ internal StorableItemDefinition(S1ItemFramework.StorableItemDefinition definition)
+ : base(definition)
+ {
+ S1StorableItemDefinition = definition;
+ }
+
+ ///
+ /// INTERNAL: A reference to the native game storable item definition.
+ ///
+ internal S1ItemFramework.StorableItemDefinition S1StorableItemDefinition { get; }
+
+ ///
+ /// The base purchase price for this item in shops.
+ ///
+ public float BasePurchasePrice
+ {
+ get => S1StorableItemDefinition.BasePurchasePrice;
+ set => S1StorableItemDefinition.BasePurchasePrice = value;
+ }
+
+ ///
+ /// The resell multiplier (0.0 to 1.0) that determines how much of the purchase price
+ /// can be recovered when selling the item.
+ ///
+ public float ResellMultiplier
+ {
+ get => S1StorableItemDefinition.ResellMultiplier;
+ set => S1StorableItemDefinition.ResellMultiplier = value;
+ }
+
+ ///
+ /// Gets whether this item is currently unlocked (available for purchase/use).
+ ///
+ public bool IsUnlocked =>
+ S1StorableItemDefinition.IsUnlocked;
+
+ ///
+ /// Whether purchasing this item requires the player to be at or above a certain level.
+ ///
+ public bool RequiresLevelToPurchase
+ {
+ get => S1StorableItemDefinition.RequiresLevelToPurchase;
+ set => S1StorableItemDefinition.RequiresLevelToPurchase = value;
+ }
+
+ ///
+ /// The required player level to purchase this item, if is true.
+ ///
+ public FullRank RequiredRank
+ {
+ get => FullRank.FromNative(S1StorableItemDefinition.RequiredRank);
+ set => S1StorableItemDefinition.RequiredRank = value.ToNative();
+ }
+
+ ///
+ /// Gets whether this item has a StationItem assigned (used by station/minigame tasks, e.g., Chemistry Station).
+ ///
+ public bool HasStationItem =>
+ S1StorableItemDefinition.StationItem != null;
+
+ ///
+ /// Gets the StationItem prefab GameObject for this item, if any.
+ ///
+ ///
+ /// This is primarily used for debugging and tooling. Prefer configuring StationItem via
+ /// during build/registration.
+ ///
+ public GameObject? StationItemPrefab =>
+ S1StorableItemDefinition.StationItem?.gameObject;
+ }
+}
diff --git a/S1API/Items/StorableItemDefinition.cs b/S1API/Items/StorableItemDefinition.cs
index 0e3701c..38f5d8e 100644
--- a/S1API/Items/StorableItemDefinition.cs
+++ b/S1API/Items/StorableItemDefinition.cs
@@ -4,6 +4,7 @@
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
+using System;
using S1API.Leveling;
using UnityEngine;
@@ -17,6 +18,7 @@ namespace S1API.Items
/// In Schedule One, all items are StorableItemDefinition or subclasses thereof.
/// The base ItemDefinition class is abstract and not used directly in gameplay.
///
+ [Obsolete("Use S1API.Items.Storable.StorableItemDefinition instead")]
public class StorableItemDefinition : ItemDefinition
{
///
diff --git a/S1API/Items/StorableItemDefinitionBuilder.cs b/S1API/Items/StorableItemDefinitionBuilder.cs
index 867099b..0a1cffb 100644
--- a/S1API/Items/StorableItemDefinitionBuilder.cs
+++ b/S1API/Items/StorableItemDefinitionBuilder.cs
@@ -30,7 +30,7 @@ namespace S1API.Items
/// All items in Schedule One are StorableItemDefinition (or subclasses thereof).
/// The base ItemDefinition class is never used directly in the game.
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.ItemBuilders.StorableItemDefinitionBuilder instead")]
public sealed class StorableItemDefinitionBuilder
{
private static readonly Log Logger = new Log("StorableItemDefinitionBuilder");
From 6517f044500215f75b562b280de562242959d545 Mon Sep 17 00:00:00 2001
From: k0 <21180271+k073l@users.noreply.github.com>
Date: Mon, 1 Jun 2026 21:52:52 +0200
Subject: [PATCH 3/7] feat(items): improve docstrings, add missing obsolete
comments
---
S1API/Items/BuildableItemCreator.cs | 2 +-
S1API/Items/BuildableItemDefinition.cs | 2 +-
S1API/Items/BuildableItemDefinitionBuilder.cs | 2 +-
S1API/Items/ClothingApplicationType.cs | 2 +-
S1API/Items/ClothingColor.cs | 2 +-
S1API/Items/ClothingItemCreator.cs | 2 +-
S1API/Items/ClothingItemDefinition.cs | 2 +-
S1API/Items/ClothingItemDefinitionBuilder.cs | 2 +-
S1API/Items/ClothingItemInstance.cs | 2 +-
S1API/Items/ClothingSlot.cs | 2 +-
.../ItemBuilders/AdditiveDefinitionBuilder.cs | 2 +-
.../BuildableItemDefinitionBuilder.cs | 2 +-
.../ClothingItemDefinitionBuilder.cs | 2 +-
.../QualityItemDefinitionBuilder.cs | 2 +-
.../StorableItemDefinitionBuilder.cs | 24 ++++++++++++-------
15 files changed, 30 insertions(+), 22 deletions(-)
diff --git a/S1API/Items/BuildableItemCreator.cs b/S1API/Items/BuildableItemCreator.cs
index 7d00446..ed78532 100644
--- a/S1API/Items/BuildableItemCreator.cs
+++ b/S1API/Items/BuildableItemCreator.cs
@@ -18,7 +18,7 @@ namespace S1API.Items
/// Use for creating items from scratch,
/// or for creating variants of existing buildable items.
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.Buildable.BuildableItemCreator instead")]
public static class BuildableItemCreator
{
///
diff --git a/S1API/Items/BuildableItemDefinition.cs b/S1API/Items/BuildableItemDefinition.cs
index 77c4fa9..b190ea9 100644
--- a/S1API/Items/BuildableItemDefinition.cs
+++ b/S1API/Items/BuildableItemDefinition.cs
@@ -17,7 +17,7 @@ namespace S1API.Items
/// Use to create new buildable items,
/// or to create variants of existing items.
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.Buildable.BuildableItemDefinition instead")]
public sealed class BuildableItemDefinition : StorableItemDefinition
{
///
diff --git a/S1API/Items/BuildableItemDefinitionBuilder.cs b/S1API/Items/BuildableItemDefinitionBuilder.cs
index bafe1b8..1d14141 100644
--- a/S1API/Items/BuildableItemDefinitionBuilder.cs
+++ b/S1API/Items/BuildableItemDefinitionBuilder.cs
@@ -17,7 +17,7 @@ namespace S1API.Items
/// Builder for composing buildable item definitions at runtime.
/// Use fluent methods to configure buildable item properties before calling .
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.ItemBuilders.BuildableItemDefinitionBuilder instead")]
public sealed class BuildableItemDefinitionBuilder
{
private readonly S1ItemFramework.BuildableItemDefinition _definition;
diff --git a/S1API/Items/ClothingApplicationType.cs b/S1API/Items/ClothingApplicationType.cs
index 22b7801..e1859ef 100644
--- a/S1API/Items/ClothingApplicationType.cs
+++ b/S1API/Items/ClothingApplicationType.cs
@@ -6,7 +6,7 @@ namespace S1API.Items
/// Represents how a clothing item is applied to the avatar.
/// Mirrors ScheduleOne.Clothing.EClothingApplicationType.
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.Clothing.ClothingApplicationType instead")]
public enum ClothingApplicationType
{
/// Applied as a body layer (flat texture on body mesh).
diff --git a/S1API/Items/ClothingColor.cs b/S1API/Items/ClothingColor.cs
index a349391..7e24164 100644
--- a/S1API/Items/ClothingColor.cs
+++ b/S1API/Items/ClothingColor.cs
@@ -6,7 +6,7 @@ namespace S1API.Items
/// Represents available clothing colors.
/// Mirrors ScheduleOne.Clothing.EClothingColor.
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.Clothing.ClothingColor instead")]
public enum ClothingColor
{
White = 0,
diff --git a/S1API/Items/ClothingItemCreator.cs b/S1API/Items/ClothingItemCreator.cs
index 2bb4db4..c92f1e8 100644
--- a/S1API/Items/ClothingItemCreator.cs
+++ b/S1API/Items/ClothingItemCreator.cs
@@ -17,7 +17,7 @@ namespace S1API.Items
/// Provides convenient static methods for creating custom clothing items.
/// Use for flexible configuration or for variants.
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.Clothing.ClothingItemCreator instead")]
public static class ClothingItemCreator
{
private static readonly Log Logger = new Log("ClothingItemCreator");
diff --git a/S1API/Items/ClothingItemDefinition.cs b/S1API/Items/ClothingItemDefinition.cs
index fbd07a8..ea100ec 100644
--- a/S1API/Items/ClothingItemDefinition.cs
+++ b/S1API/Items/ClothingItemDefinition.cs
@@ -19,7 +19,7 @@ namespace S1API.Items
/// Use to create new clothing items,
/// or to create variants of existing items.
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.Clothing.ClothingItemDefinition instead")]
public sealed class ClothingItemDefinition : StorableItemDefinition
{
///
diff --git a/S1API/Items/ClothingItemDefinitionBuilder.cs b/S1API/Items/ClothingItemDefinitionBuilder.cs
index ee93b7e..2d4afd0 100644
--- a/S1API/Items/ClothingItemDefinitionBuilder.cs
+++ b/S1API/Items/ClothingItemDefinitionBuilder.cs
@@ -26,7 +26,7 @@ namespace S1API.Items
/// Builder for composing clothing item definitions at runtime.
/// Use fluent methods to configure clothing properties before calling .
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.ItemBuilders.ClothingItemDefinitionBuilder instead")]
public sealed class ClothingItemDefinitionBuilder
{
private static readonly Log Logger = new Log("ClothingItemDefinitionBuilder");
diff --git a/S1API/Items/ClothingItemInstance.cs b/S1API/Items/ClothingItemInstance.cs
index b59031e..f1e3e4c 100644
--- a/S1API/Items/ClothingItemInstance.cs
+++ b/S1API/Items/ClothingItemInstance.cs
@@ -11,7 +11,7 @@ namespace S1API.Items
/// Represents a clothing item instance in the game world (physical clothing you own).
/// Extends with color information.
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.Clothing.ClothingItemInstance instead")]
public class ClothingItemInstance : ItemInstance
{
///
diff --git a/S1API/Items/ClothingSlot.cs b/S1API/Items/ClothingSlot.cs
index 7238a65..9ef7360 100644
--- a/S1API/Items/ClothingSlot.cs
+++ b/S1API/Items/ClothingSlot.cs
@@ -6,7 +6,7 @@ namespace S1API.Items
/// Represents the slot where a clothing item can be equipped.
/// Mirrors ScheduleOne.Clothing.EClothingSlot.
///
- [Obsolete]
+ [Obsolete("Use S1API.Items.Clothing.ClothingSlot instead")]
public enum ClothingSlot
{
/// Feet slot (shoes, boots).
diff --git a/S1API/Items/ItemBuilders/AdditiveDefinitionBuilder.cs b/S1API/Items/ItemBuilders/AdditiveDefinitionBuilder.cs
index a21db04..4a9ac7d 100644
--- a/S1API/Items/ItemBuilders/AdditiveDefinitionBuilder.cs
+++ b/S1API/Items/ItemBuilders/AdditiveDefinitionBuilder.cs
@@ -23,7 +23,7 @@ namespace S1API.Items.ItemBuilders
/// Use fluent methods to configure additive properties before calling .
///
public sealed class AdditiveDefinitionBuilder
- : StorableItemDefinitionBuilder
+ : StorableItemDefinitionBuilderBase
{
private static readonly Log Logger = new Log("AdditiveDefinitionBuilder");
diff --git a/S1API/Items/ItemBuilders/BuildableItemDefinitionBuilder.cs b/S1API/Items/ItemBuilders/BuildableItemDefinitionBuilder.cs
index 1d98c89..ce72c64 100644
--- a/S1API/Items/ItemBuilders/BuildableItemDefinitionBuilder.cs
+++ b/S1API/Items/ItemBuilders/BuildableItemDefinitionBuilder.cs
@@ -17,7 +17,7 @@ namespace S1API.Items.ItemBuilders
/// Use fluent methods to configure buildable item properties before calling .
///
public class BuildableItemDefinitionBuilder
- : StorableItemDefinitionBuilder
+ : StorableItemDefinitionBuilderBase
{
private S1ItemFramework.BuildableItemDefinition BuildableDefinition =>
CrossType.As(Definition);
diff --git a/S1API/Items/ItemBuilders/ClothingItemDefinitionBuilder.cs b/S1API/Items/ItemBuilders/ClothingItemDefinitionBuilder.cs
index 9c60d81..4aa7976 100644
--- a/S1API/Items/ItemBuilders/ClothingItemDefinitionBuilder.cs
+++ b/S1API/Items/ItemBuilders/ClothingItemDefinitionBuilder.cs
@@ -27,7 +27,7 @@ namespace S1API.Items.ItemBuilders
/// Use fluent methods to configure clothing properties before calling .
///
public class ClothingItemDefinitionBuilder
- : StorableItemDefinitionBuilder
+ : StorableItemDefinitionBuilderBase
{
private S1Clothing.ClothingDefinition ClothingDefinition =>
CrossType.As(Definition);
diff --git a/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs b/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs
index 8a448c1..04937fd 100644
--- a/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs
+++ b/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs
@@ -14,7 +14,7 @@ namespace S1API.Items.ItemBuilders
/// Use fluent methods to configure item properties before calling
///
public sealed class QualityItemDefinitionBuilder
- : StorableItemDefinitionBuilder
+ : StorableItemDefinitionBuilderBase
{
private S1ItemFramework.QualityItemDefinition QualityDefinition =>
CrossType.As(Definition);
diff --git a/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs b/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs
index 570fe5e..c2d84b9 100644
--- a/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs
+++ b/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs
@@ -30,7 +30,7 @@ namespace S1API.Items.ItemBuilders
/// The base ItemDefinition class is never used directly in the game.
///
public sealed class StorableItemDefinitionBuilder
- : StorableItemDefinitionBuilder
+ : StorableItemDefinitionBuilderBase
{
///
internal StorableItemDefinitionBuilder(
@@ -56,7 +56,7 @@ internal StorableItemDefinitionBuilder()
}
///
- /// State class for managing shared resources and caches used by .
+ /// State class for managing shared resources and caches used by .
///
internal static class StorableItemDefinitionBuilderState
{
@@ -71,11 +71,11 @@ internal static class StorableItemDefinitionBuilderState
}
///
- /// Generic base builder for composing item definitions at runtime, with fluent methods returning the correct subclass type.
+ /// INTERNAL: Generic base builder for composing item definitions at runtime, with fluent methods returning the correct subclass type.
///
/// The concrete builder type being implemented (e.g., StorableItemDefinitionBuilder).
- public abstract class StorableItemDefinitionBuilder
- where TSelf : StorableItemDefinitionBuilder
+ public abstract class StorableItemDefinitionBuilderBase
+ where TSelf : StorableItemDefinitionBuilderBase
{
private static Log Logger => StorableItemDefinitionBuilderState.Logger;
private static object StationItemGate => StorableItemDefinitionBuilderState.StationItemGate;
@@ -105,7 +105,7 @@ private static GameObject StationItemRoot
/// INTERNAL: Creates a new builder instance with a fresh StorableItemDefinition.
/// Only ItemCreator can instantiate this.
///
- internal StorableItemDefinitionBuilder(
+ internal StorableItemDefinitionBuilderBase(
Func? definitionFactory = null)
{
Definition = definitionFactory != null
@@ -123,11 +123,13 @@ internal StorableItemDefinitionBuilder(
///
/// The existing item definition to clone properties from.
/// Optional factory function to create the definition instance. If null, a default StorableItemDefinition will be created.
- internal StorableItemDefinitionBuilder(
+ internal StorableItemDefinitionBuilderBase(
S1ItemFramework.StorableItemDefinition source,
Func? definitionFactory = null)
: this(definitionFactory)
{
+ // Intentionally virtual - Overrides should operate only
+ // on fields initialized by chained constructor i.e. Definition
CopyPropertiesFrom(source);
}
@@ -365,7 +367,10 @@ public TSelf WithRequiredRank(Leveling.FullRank? rank)
/// Builds the item definition, registers it with the game's registry, and returns a wrapper.
///
/// A wrapper around the created storable item definition.
- public virtual Storable.StorableItemDefinition Build()
+ ///
+ /// Designated virtual, usually shadowed in subclasses due to different return type.
+ ///
+ protected virtual Storable.StorableItemDefinition Build()
{
if (!_hasCustomStoredItem && Definition.StoredItem != null)
{
@@ -387,6 +392,9 @@ public virtual Storable.StorableItemDefinition Build()
/// INTERNAL: Builds and returns the raw game item definition without registering.
/// Used internally by S1API. Modders should use instead.
///
+ ///
+ /// Designated virtual, usually shadowed in subclasses due to different return type.
+ ///
internal virtual S1ItemFramework.StorableItemDefinition BuildInternal()
{
return Definition;
From 97739253e5fbeccffca3ead927ba9fe7dcaefc30 Mon Sep 17 00:00:00 2001
From: k0 <21180271+k073l@users.noreply.github.com>
Date: Thu, 4 Jun 2026 13:34:18 +0200
Subject: [PATCH 4/7] refactor(items): move builder namespaces
---
.../AdditiveDefinitionBuilder.cs | 16 +++++-----------
S1API/Items/Additive/AdditiveItemCreator.cs | 12 ++++++------
S1API/Items/AdditiveDefinitionBuilder.cs | 2 +-
S1API/Items/Buildable/BuildableItemCreator.cs | 12 ++++++------
.../BuildableItemDefinitionBuilder.cs | 15 +++++++--------
S1API/Items/BuildableItemDefinitionBuilder.cs | 2 +-
S1API/Items/Clothing/ClothingItemCreator.cs | 12 ++++++------
.../ClothingItemDefinitionBuilder.cs | 12 +++++-------
S1API/Items/Clothing/ClothingItemInstance.cs | 6 ++++--
S1API/Items/ClothingItemDefinitionBuilder.cs | 2 +-
S1API/Items/Quality/QualityItemCreator.cs | 12 ++++++------
.../QualityItemDefinitionBuilder.cs | 14 +++++++-------
S1API/Items/Quality/QualityItemInstance.cs | 10 ++++++----
S1API/Items/QualityItemDefinitionBuilder.cs | 2 +-
S1API/Items/Storable/ItemCreator.cs | 14 +++++++-------
.../StorableItemDefinitionBuilder.cs | 10 +++++-----
S1API/Items/StorableItemDefinitionBuilder.cs | 2 +-
17 files changed, 75 insertions(+), 80 deletions(-)
rename S1API/Items/{ItemBuilders => Additive}/AdditiveDefinitionBuilder.cs (92%)
rename S1API/Items/{ItemBuilders => Buildable}/BuildableItemDefinitionBuilder.cs (89%)
rename S1API/Items/{ItemBuilders => Clothing}/ClothingItemDefinitionBuilder.cs (97%)
rename S1API/Items/{ItemBuilders => Quality}/QualityItemDefinitionBuilder.cs (89%)
rename S1API/Items/{ItemBuilders => Storable}/StorableItemDefinitionBuilder.cs (98%)
diff --git a/S1API/Items/ItemBuilders/AdditiveDefinitionBuilder.cs b/S1API/Items/Additive/AdditiveDefinitionBuilder.cs
similarity index 92%
rename from S1API/Items/ItemBuilders/AdditiveDefinitionBuilder.cs
rename to S1API/Items/Additive/AdditiveDefinitionBuilder.cs
index 4a9ac7d..3fe9449 100644
--- a/S1API/Items/ItemBuilders/AdditiveDefinitionBuilder.cs
+++ b/S1API/Items/Additive/AdditiveDefinitionBuilder.cs
@@ -1,22 +1,16 @@
#if (IL2CPPMELON)
using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
using S1CoreItemFramework = Il2CppScheduleOne.Core.Items.Framework;
-using S1Registry = Il2CppScheduleOne.Registry;
-using S1Storage = Il2CppScheduleOne.Storage;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1ItemFramework = ScheduleOne.ItemFramework;
using S1CoreItemFramework = ScheduleOne.Core.Items.Framework;
-using S1Registry = ScheduleOne.Registry;
-using S1Storage = ScheduleOne.Storage;
#endif
-
-using System;
using S1API.Internal.Utils;
+using S1API.Items.Storable;
using S1API.Logging;
using UnityEngine;
-using Object = UnityEngine.Object;
-namespace S1API.Items.ItemBuilders
+namespace S1API.Items.Additive
{
///
/// Builder for composing additive definitions at runtime.
@@ -117,9 +111,9 @@ public AdditiveDefinitionBuilder WithEffects(float yieldMultiplier, float instan
/// Builds the item definition, registers it with the game's registry, and returns a wrapper.
///
/// A wrapper around the created additive definition.
- public new Additive.AdditiveDefinition Build()
+ public new AdditiveDefinition Build()
{
- return (Additive.AdditiveDefinition)base.Build();
+ return (AdditiveDefinition)base.Build();
}
///
@@ -135,7 +129,7 @@ public AdditiveDefinitionBuilder WithEffects(float yieldMultiplier, float instan
protected override Storable.StorableItemDefinition CreateWrapper(
S1ItemFramework.StorableItemDefinition definition)
{
- return new Additive.AdditiveDefinition(AdditiveDefinition);
+ return new AdditiveDefinition(AdditiveDefinition);
}
}
}
\ No newline at end of file
diff --git a/S1API/Items/Additive/AdditiveItemCreator.cs b/S1API/Items/Additive/AdditiveItemCreator.cs
index 3b03251..4467077 100644
--- a/S1API/Items/Additive/AdditiveItemCreator.cs
+++ b/S1API/Items/Additive/AdditiveItemCreator.cs
@@ -20,9 +20,9 @@ public static class AdditiveItemCreator
/// Creates a new builder for composing an additive definition with full flexibility.
/// Use fluent methods to configure the additive, then call Build() to register it.
///
- public static ItemBuilders.AdditiveDefinitionBuilder CreateBuilder()
+ public static AdditiveDefinitionBuilder CreateBuilder()
{
- return new ItemBuilders.AdditiveDefinitionBuilder();
+ return new AdditiveDefinitionBuilder();
}
///
@@ -31,7 +31,7 @@ public static ItemBuilders.AdditiveDefinitionBuilder CreateBuilder()
/// The ID of the additive to clone.
/// A builder pre-configured with the source additive's properties.
/// Thrown if the source item does not exist or is not an additive.
- public static ItemBuilders.AdditiveDefinitionBuilder CloneFrom(string sourceItemId)
+ public static AdditiveDefinitionBuilder CloneFrom(string sourceItemId)
{
var sourceDefinition = S1Registry.GetItem(sourceItemId);
if (sourceDefinition == null)
@@ -44,7 +44,7 @@ public static ItemBuilders.AdditiveDefinitionBuilder CloneFrom(string sourceItem
throw new ArgumentException($"Item '{sourceItemId}' is not an AdditiveDefinition", nameof(sourceItemId));
}
- return new ItemBuilders.AdditiveDefinitionBuilder(additiveDef);
+ return new AdditiveDefinitionBuilder(additiveDef);
}
///
@@ -52,14 +52,14 @@ public static ItemBuilders.AdditiveDefinitionBuilder CloneFrom(string sourceItem
///
/// The additive definition to clone from.
/// A builder pre-configured with the source additive's properties.
- public static ItemBuilders.AdditiveDefinitionBuilder CloneFrom(AdditiveDefinition source)
+ public static AdditiveDefinitionBuilder CloneFrom(AdditiveDefinition source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source), "Source additive definition cannot be null");
}
- return new ItemBuilders.AdditiveDefinitionBuilder(source.S1AdditiveDefinition);
+ return new AdditiveDefinitionBuilder(source.S1AdditiveDefinition);
}
}
}
diff --git a/S1API/Items/AdditiveDefinitionBuilder.cs b/S1API/Items/AdditiveDefinitionBuilder.cs
index 86c3e76..00ffcfe 100644
--- a/S1API/Items/AdditiveDefinitionBuilder.cs
+++ b/S1API/Items/AdditiveDefinitionBuilder.cs
@@ -22,7 +22,7 @@ namespace S1API.Items
/// Builder for composing additive definitions at runtime.
/// Use fluent methods to configure additive properties before calling .
///
- [Obsolete("Use S1API.Items.ItemBuilders.AdditiveDefinitionBuilder instead")]
+ [Obsolete("Use S1API.Items.Additive.AdditiveDefinitionBuilder instead")]
public sealed class AdditiveDefinitionBuilder
{
private static readonly Log Logger = new Log("AdditiveDefinitionBuilder");
diff --git a/S1API/Items/Buildable/BuildableItemCreator.cs b/S1API/Items/Buildable/BuildableItemCreator.cs
index 125d781..66055b2 100644
--- a/S1API/Items/Buildable/BuildableItemCreator.cs
+++ b/S1API/Items/Buildable/BuildableItemCreator.cs
@@ -33,9 +33,9 @@ public static class BuildableItemCreator
/// .Build();
///
///
- public static ItemBuilders.BuildableItemDefinitionBuilder CreateBuilder()
+ public static BuildableItemDefinitionBuilder CreateBuilder()
{
- return new ItemBuilders.BuildableItemDefinitionBuilder();
+ return new BuildableItemDefinitionBuilder();
}
///
@@ -53,7 +53,7 @@ public static ItemBuilders.BuildableItemDefinitionBuilder CreateBuilder()
/// .Build();
///
///
- public static ItemBuilders.BuildableItemDefinitionBuilder CloneFrom(string sourceItemId)
+ public static BuildableItemDefinitionBuilder CloneFrom(string sourceItemId)
{
var sourceDefinition = S1Registry.GetItem(sourceItemId);
@@ -74,7 +74,7 @@ public static ItemBuilders.BuildableItemDefinitionBuilder CloneFrom(string sourc
);
}
- return new ItemBuilders.BuildableItemDefinitionBuilder(buildableDef);
+ return new BuildableItemDefinitionBuilder(buildableDef);
}
///
@@ -91,14 +91,14 @@ public static ItemBuilders.BuildableItemDefinitionBuilder CloneFrom(string sourc
/// .Build();
///
///
- public static ItemBuilders.BuildableItemDefinitionBuilder CloneFrom(BuildableItemDefinition source)
+ public static BuildableItemDefinitionBuilder CloneFrom(BuildableItemDefinition source)
{
if (source == null)
{
throw new System.ArgumentNullException(nameof(source), "Source item definition cannot be null");
}
- return new ItemBuilders.BuildableItemDefinitionBuilder(source.S1BuildableItemDefinition);
+ return new BuildableItemDefinitionBuilder(source.S1BuildableItemDefinition);
}
}
}
diff --git a/S1API/Items/ItemBuilders/BuildableItemDefinitionBuilder.cs b/S1API/Items/Buildable/BuildableItemDefinitionBuilder.cs
similarity index 89%
rename from S1API/Items/ItemBuilders/BuildableItemDefinitionBuilder.cs
rename to S1API/Items/Buildable/BuildableItemDefinitionBuilder.cs
index ce72c64..87de62d 100644
--- a/S1API/Items/ItemBuilders/BuildableItemDefinitionBuilder.cs
+++ b/S1API/Items/Buildable/BuildableItemDefinitionBuilder.cs
@@ -5,12 +5,11 @@
using S1ItemFramework = ScheduleOne.ItemFramework;
using S1CoreItemFramework = ScheduleOne.Core.Items.Framework;
#endif
-
-using System;
using S1API.Internal.Utils;
+using S1API.Items.Storable;
using UnityEngine;
-namespace S1API.Items.ItemBuilders
+namespace S1API.Items.Buildable
{
///
/// Builder for composing buildable item definitions at runtime.
@@ -24,7 +23,7 @@ public class BuildableItemDefinitionBuilder
///
/// INTERNAL: Creates a new builder instance with a fresh BuildableItemDefinition.
- /// Only BuildableItemCreator can instantiate this.
+ /// Only can instantiate this.
///
internal BuildableItemDefinitionBuilder()
: base(ScriptableObject.CreateInstance)
@@ -57,7 +56,7 @@ protected override void CopyPropertiesFrom(S1ItemFramework.StorableItemDefinitio
///
/// The build sound type.
/// The builder instance for fluent chaining.
- public BuildableItemDefinitionBuilder WithBuildSound(BuildSoundType soundType)
+ public BuildableItemDefinitionBuilder WithBuildSound(Items.BuildSoundType soundType)
{
BuildableDefinition.BuildSoundType = (S1ItemFramework.BuildableItemDefinition.EBuildSoundType)soundType;
return this;
@@ -67,9 +66,9 @@ public BuildableItemDefinitionBuilder WithBuildSound(BuildSoundType soundType)
/// Builds the item definition, registers it with the game's registry, and returns a wrapper.
///
/// A wrapper around the created buildable item definition.
- public new Buildable.BuildableItemDefinition Build()
+ public new BuildableItemDefinition Build()
{
- return (Buildable.BuildableItemDefinition)base.Build();
+ return (BuildableItemDefinition)base.Build();
}
///
@@ -85,7 +84,7 @@ public BuildableItemDefinitionBuilder WithBuildSound(BuildSoundType soundType)
protected override Storable.StorableItemDefinition CreateWrapper(
S1ItemFramework.StorableItemDefinition definition)
{
- return new Buildable.BuildableItemDefinition(BuildableDefinition);
+ return new BuildableItemDefinition(BuildableDefinition);
}
}
}
\ No newline at end of file
diff --git a/S1API/Items/BuildableItemDefinitionBuilder.cs b/S1API/Items/BuildableItemDefinitionBuilder.cs
index 1d14141..1ef4450 100644
--- a/S1API/Items/BuildableItemDefinitionBuilder.cs
+++ b/S1API/Items/BuildableItemDefinitionBuilder.cs
@@ -17,7 +17,7 @@ namespace S1API.Items
/// Builder for composing buildable item definitions at runtime.
/// Use fluent methods to configure buildable item properties before calling .
///
- [Obsolete("Use S1API.Items.ItemBuilders.BuildableItemDefinitionBuilder instead")]
+ [Obsolete("Use S1API.Items.Buildable.BuildableItemDefinitionBuilder instead")]
public sealed class BuildableItemDefinitionBuilder
{
private readonly S1ItemFramework.BuildableItemDefinition _definition;
diff --git a/S1API/Items/Clothing/ClothingItemCreator.cs b/S1API/Items/Clothing/ClothingItemCreator.cs
index 517814f..4bbec74 100644
--- a/S1API/Items/Clothing/ClothingItemCreator.cs
+++ b/S1API/Items/Clothing/ClothingItemCreator.cs
@@ -35,9 +35,9 @@ public static class ClothingItemCreator
/// .Build();
///
///
- public static ItemBuilders.ClothingItemDefinitionBuilder CreateBuilder()
+ public static ClothingItemDefinitionBuilder CreateBuilder()
{
- return new ItemBuilders.ClothingItemDefinitionBuilder();
+ return new ClothingItemDefinitionBuilder();
}
///
@@ -57,7 +57,7 @@ public static ItemBuilders.ClothingItemDefinitionBuilder CreateBuilder()
/// .Build();
///
///
- public static ItemBuilders.ClothingItemDefinitionBuilder CloneFrom(string sourceItemId)
+ public static ClothingItemDefinitionBuilder CloneFrom(string sourceItemId)
{
var sourceDefinition = S1.Registry.GetItem(sourceItemId);
if (sourceDefinition == null)
@@ -73,7 +73,7 @@ public static ItemBuilders.ClothingItemDefinitionBuilder CloneFrom(string source
return null;
}
- return new ItemBuilders.ClothingItemDefinitionBuilder(clothingDef);
+ return new ClothingItemDefinitionBuilder(clothingDef);
}
///
@@ -90,7 +90,7 @@ public static ItemBuilders.ClothingItemDefinitionBuilder CloneFrom(string source
/// .Build();
///
///
- public static ItemBuilders.ClothingItemDefinitionBuilder CloneFrom(ClothingItemDefinition source)
+ public static ClothingItemDefinitionBuilder CloneFrom(ClothingItemDefinition source)
{
if (source == null)
{
@@ -98,7 +98,7 @@ public static ItemBuilders.ClothingItemDefinitionBuilder CloneFrom(ClothingItemD
return null;
}
- return new ItemBuilders.ClothingItemDefinitionBuilder(source.S1ClothingDefinition);
+ return new ClothingItemDefinitionBuilder(source.S1ClothingDefinition);
}
}
}
diff --git a/S1API/Items/ItemBuilders/ClothingItemDefinitionBuilder.cs b/S1API/Items/Clothing/ClothingItemDefinitionBuilder.cs
similarity index 97%
rename from S1API/Items/ItemBuilders/ClothingItemDefinitionBuilder.cs
rename to S1API/Items/Clothing/ClothingItemDefinitionBuilder.cs
index 4aa7976..fde0f8a 100644
--- a/S1API/Items/ItemBuilders/ClothingItemDefinitionBuilder.cs
+++ b/S1API/Items/Clothing/ClothingItemDefinitionBuilder.cs
@@ -11,16 +11,14 @@
using S1CoreItemFramework = ScheduleOne.Core.Items.Framework;
using S1Registry = ScheduleOne.Registry;
using S1UiItems = ScheduleOne.UI.Items;
-using Il2CppCollections = System.Collections.Generic;
#endif
-
-using System;
using System.Collections.Generic;
using S1API.Internal.Utils;
+using S1API.Items.Storable;
using S1API.Logging;
using UnityEngine;
-namespace S1API.Items.ItemBuilders
+namespace S1API.Items.Clothing
{
///
/// Builder for composing clothing item definitions at runtime.
@@ -177,10 +175,10 @@ public ClothingItemDefinitionBuilder WithBlockedSlots(params Clothing.ClothingCo
/// Builds the item definition, registers it with the game's registry, and returns a wrapper.
///
/// A wrapper around the created clothing item definition.
- public new Clothing.ClothingItemDefinition Build()
+ public new ClothingItemDefinition Build()
{
EnsureNativeClothingItemUi();
- return (Clothing.ClothingItemDefinition)base.Build();
+ return (ClothingItemDefinition)base.Build();
}
///
@@ -197,7 +195,7 @@ public ClothingItemDefinitionBuilder WithBlockedSlots(params Clothing.ClothingCo
protected override Storable.StorableItemDefinition CreateWrapper(
S1ItemFramework.StorableItemDefinition definition)
{
- return new Clothing.ClothingItemDefinition(ClothingDefinition);
+ return new ClothingItemDefinition(ClothingDefinition);
}
diff --git a/S1API/Items/Clothing/ClothingItemInstance.cs b/S1API/Items/Clothing/ClothingItemInstance.cs
index 3efa0e4..55ca5e5 100644
--- a/S1API/Items/Clothing/ClothingItemInstance.cs
+++ b/S1API/Items/Clothing/ClothingItemInstance.cs
@@ -4,6 +4,7 @@
using S1Clothing = ScheduleOne.Clothing;
#endif
using System;
+using S1API.Internal.Utils;
namespace S1API.Items.Clothing
{
@@ -22,7 +23,7 @@ public class ClothingItemInstance : ItemInstance
/// INTERNAL: Creates a ClothingItemInstance wrapper.
///
/// In-game clothing item instance
- internal ClothingItemInstance(S1Clothing.ClothingInstance itemInstance)
+ internal ClothingItemInstance(S1Clothing.ClothingInstance itemInstance)
: base(itemInstance)
{
S1ClothingInstance = itemInstance;
@@ -41,6 +42,7 @@ public ClothingColor Color
/// The clothing definition (template) this instance was created from.
///
public new ClothingItemDefinition Definition =>
- new ClothingItemDefinition((S1Clothing.ClothingDefinition)S1ClothingInstance.Definition);
+ new ClothingItemDefinition(
+ CrossType.As(S1ClothingInstance.Definition));
}
}
\ No newline at end of file
diff --git a/S1API/Items/ClothingItemDefinitionBuilder.cs b/S1API/Items/ClothingItemDefinitionBuilder.cs
index 2d4afd0..1a413ce 100644
--- a/S1API/Items/ClothingItemDefinitionBuilder.cs
+++ b/S1API/Items/ClothingItemDefinitionBuilder.cs
@@ -26,7 +26,7 @@ namespace S1API.Items
/// Builder for composing clothing item definitions at runtime.
/// Use fluent methods to configure clothing properties before calling .
///
- [Obsolete("Use S1API.Items.ItemBuilders.ClothingItemDefinitionBuilder instead")]
+ [Obsolete("Use S1API.Items.Clothing.ClothingItemDefinitionBuilder instead")]
public sealed class ClothingItemDefinitionBuilder
{
private static readonly Log Logger = new Log("ClothingItemDefinitionBuilder");
diff --git a/S1API/Items/Quality/QualityItemCreator.cs b/S1API/Items/Quality/QualityItemCreator.cs
index 470aa0a..e0fd3e8 100644
--- a/S1API/Items/Quality/QualityItemCreator.cs
+++ b/S1API/Items/Quality/QualityItemCreator.cs
@@ -21,9 +21,9 @@ public class QualityItemCreator
/// Creates a new builder for composing a quality item definition with full flexibility.
/// Use fluent methods to configure the definition, then call Build() to register it.
///
- public static ItemBuilders.QualityItemDefinitionBuilder CreateBuilder()
+ public static QualityItemDefinitionBuilder CreateBuilder()
{
- return new ItemBuilders.QualityItemDefinitionBuilder();
+ return new QualityItemDefinitionBuilder();
}
///
@@ -32,7 +32,7 @@ public static ItemBuilders.QualityItemDefinitionBuilder CreateBuilder()
/// The ID of the item to clone.
/// A builder pre-configured with the source item properties.
/// Thrown if the source item ID is not found or is not a quality item.
- public static ItemBuilders.QualityItemDefinitionBuilder CloneFrom(string sourceItemId)
+ public static QualityItemDefinitionBuilder CloneFrom(string sourceItemId)
{
var sourceDefinition = S1Registry.GetItem(sourceItemId);
if (sourceDefinition == null)
@@ -45,7 +45,7 @@ public static ItemBuilders.QualityItemDefinitionBuilder CloneFrom(string sourceI
throw new ArgumentException($"Item '{sourceItemId}' is not an QualityItemDefinition", nameof(sourceItemId));
}
- return new ItemBuilders.QualityItemDefinitionBuilder(qualityDef);
+ return new QualityItemDefinitionBuilder(qualityDef);
}
///
@@ -54,14 +54,14 @@ public static ItemBuilders.QualityItemDefinitionBuilder CloneFrom(string sourceI
/// The quality item definition to clone.
/// A builder pre-configured with the source item properties.
/// Thrown if the source definition is null.
- public static ItemBuilders.QualityItemDefinitionBuilder CloneFrom(QualityItemDefinition source)
+ public static QualityItemDefinitionBuilder CloneFrom(QualityItemDefinition source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source), "Source storable item definition cannot be null");
}
- return new ItemBuilders.QualityItemDefinitionBuilder(source.S1QualityDefinition);
+ return new QualityItemDefinitionBuilder(source.S1QualityDefinition);
}
}
}
\ No newline at end of file
diff --git a/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs b/S1API/Items/Quality/QualityItemDefinitionBuilder.cs
similarity index 89%
rename from S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs
rename to S1API/Items/Quality/QualityItemDefinitionBuilder.cs
index 04937fd..4fdafaf 100644
--- a/S1API/Items/ItemBuilders/QualityItemDefinitionBuilder.cs
+++ b/S1API/Items/Quality/QualityItemDefinitionBuilder.cs
@@ -4,10 +4,10 @@
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
using S1API.Internal.Utils;
-using S1API.Products;
+using S1API.Items.Storable;
using UnityEngine;
-namespace S1API.Items.ItemBuilders
+namespace S1API.Items.Quality
{
///
/// Builder for composing quality item definitions at runtime.
@@ -21,7 +21,7 @@ public sealed class QualityItemDefinitionBuilder
///
/// INTERNAL: Creates a new builder instance with a fresh QualityItemDefinition.
- /// Only QualityItemCreator can instantiate this.
+ /// Only can instantiate this.
///
internal QualityItemDefinitionBuilder()
: base(ScriptableObject.CreateInstance)
@@ -32,7 +32,7 @@ internal QualityItemDefinitionBuilder()
///
/// INTERNAL: Creates a builder instance initialized by cloning an existing quality item definition.
- /// Only QualityItemCreator can instantiate this.
+ /// Only can instantiate this.
///
/// The existing quality item definition to clone properties from.
internal QualityItemDefinitionBuilder(
@@ -69,9 +69,9 @@ public QualityItemDefinitionBuilder WithDefaultQuality(Products.Quality quality)
/// Builds the item definition, registers it with the game's registry, and returns a wrapper.
///
/// A wrapper around the created quality item definition.
- public new Quality.QualityItemDefinition Build()
+ public new QualityItemDefinition Build()
{
- return (Quality.QualityItemDefinition)base.Build();
+ return (QualityItemDefinition)base.Build();
}
///
@@ -87,7 +87,7 @@ public QualityItemDefinitionBuilder WithDefaultQuality(Products.Quality quality)
protected override Storable.StorableItemDefinition CreateWrapper(
S1ItemFramework.StorableItemDefinition definition)
{
- return new Quality.QualityItemDefinition(QualityDefinition);
+ return new QualityItemDefinition(QualityDefinition);
}
}
}
\ No newline at end of file
diff --git a/S1API/Items/Quality/QualityItemInstance.cs b/S1API/Items/Quality/QualityItemInstance.cs
index c3e705c..60b3afc 100644
--- a/S1API/Items/Quality/QualityItemInstance.cs
+++ b/S1API/Items/Quality/QualityItemInstance.cs
@@ -3,6 +3,7 @@
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
+using S1API.Internal.Utils;
namespace S1API.Items.Quality
{
@@ -16,7 +17,7 @@ public class QualityItemInstance : ItemInstance
/// INTERNAL: Reference to the in-game quality item instance.
///
internal readonly S1ItemFramework.QualityItemInstance S1QualityInstance;
-
+
///
/// INTERNAL: Creates a QualityItemInstance wrapper.
///
@@ -25,7 +26,7 @@ internal QualityItemInstance(S1ItemFramework.QualityItemInstance itemInstance) :
{
S1QualityInstance = itemInstance;
}
-
+
///
/// The quality of this item.
///
@@ -34,11 +35,12 @@ public Products.Quality Quality
get => (Products.Quality)S1QualityInstance.Quality;
set => S1QualityInstance.Quality = (S1ItemFramework.EQuality)value;
}
-
+
///
/// The quality item definition (template) this instance was created from.
///
public new QualityItemDefinition Definition =>
- new QualityItemDefinition((S1ItemFramework.QualityItemDefinition)S1QualityInstance.Definition);
+ new QualityItemDefinition(
+ CrossType.As(S1QualityInstance.Definition));
}
}
\ No newline at end of file
diff --git a/S1API/Items/QualityItemDefinitionBuilder.cs b/S1API/Items/QualityItemDefinitionBuilder.cs
index a02c21e..55f1369 100644
--- a/S1API/Items/QualityItemDefinitionBuilder.cs
+++ b/S1API/Items/QualityItemDefinitionBuilder.cs
@@ -27,7 +27,7 @@ namespace S1API.Items
/// Builder for composing quality item definitions at runtime.
/// Use fluent methods to configure item properties before calling
///
- [Obsolete("Use S1API.Items.ItemBuilders.QualityItemDefinitionBuilder instead")]
+ [Obsolete("Use S1API.Items.Quality.QualityItemDefinitionBuilder instead")]
public sealed class QualityItemDefinitionBuilder
{
private static readonly Log Logger = new Log("QualityItemDefinitionBuilder");
diff --git a/S1API/Items/Storable/ItemCreator.cs b/S1API/Items/Storable/ItemCreator.cs
index 31b7e70..202ffac 100644
--- a/S1API/Items/Storable/ItemCreator.cs
+++ b/S1API/Items/Storable/ItemCreator.cs
@@ -35,9 +35,9 @@ public static class ItemCreator
/// .Build();
///
///
- public static ItemBuilders.StorableItemDefinitionBuilder CreateBuilder()
+ public static StorableItemDefinitionBuilder CreateBuilder()
{
- return new ItemBuilders.StorableItemDefinitionBuilder();
+ return new StorableItemDefinitionBuilder();
}
///
@@ -46,7 +46,7 @@ public static ItemBuilders.StorableItemDefinitionBuilder CreateBuilder()
/// The ID of the item to clone.
/// A builder pre-configured with the source item properties.
/// Thrown if the source item ID is not found or is not a storable item.
- public static ItemBuilders.StorableItemDefinitionBuilder CloneFrom(string sourceItemId)
+ public static StorableItemDefinitionBuilder CloneFrom(string sourceItemId)
{
var sourceDefinition = S1Registry.GetItem(sourceItemId);
if (sourceDefinition == null)
@@ -59,7 +59,7 @@ public static ItemBuilders.StorableItemDefinitionBuilder CloneFrom(string source
throw new ArgumentException($"Item '{sourceItemId}' is not an StorableItemDefinition", nameof(sourceItemId));
}
- return new ItemBuilders.StorableItemDefinitionBuilder(storableDef);
+ return new StorableItemDefinitionBuilder(storableDef);
}
///
@@ -68,14 +68,14 @@ public static ItemBuilders.StorableItemDefinitionBuilder CloneFrom(string source
/// The storable item definition to clone.
/// A builder pre-configured with the source item properties.
/// Thrown if the source definition is null.
- public static ItemBuilders.StorableItemDefinitionBuilder CloneFrom(StorableItemDefinition source)
+ public static StorableItemDefinitionBuilder CloneFrom(StorableItemDefinition source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source), "Source storable item definition cannot be null");
}
- return new ItemBuilders.StorableItemDefinitionBuilder(source.S1StorableItemDefinition);
+ return new StorableItemDefinitionBuilder(source.S1StorableItemDefinition);
}
///
@@ -121,7 +121,7 @@ public static StorableItemDefinition CreateItem(
Sprite icon = null,
Equippable equippable = null)
{
- var builder = new ItemBuilders.StorableItemDefinitionBuilder()
+ var builder = new StorableItemDefinitionBuilder()
.WithBasicInfo(id, name, description, category)
.WithStackLimit(stackLimit)
.WithPricing(basePurchasePrice, resellMultiplier)
diff --git a/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs b/S1API/Items/Storable/StorableItemDefinitionBuilder.cs
similarity index 98%
rename from S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs
rename to S1API/Items/Storable/StorableItemDefinitionBuilder.cs
index c2d84b9..d641335 100644
--- a/S1API/Items/ItemBuilders/StorableItemDefinitionBuilder.cs
+++ b/S1API/Items/Storable/StorableItemDefinitionBuilder.cs
@@ -19,7 +19,7 @@
using UnityEngine;
using Object = UnityEngine.Object;
-namespace S1API.Items.ItemBuilders
+namespace S1API.Items.Storable
{
///
/// Builder for composing item definitions at runtime.
@@ -103,7 +103,7 @@ private static GameObject StationItemRoot
///
/// INTERNAL: Creates a new builder instance with a fresh StorableItemDefinition.
- /// Only ItemCreator can instantiate this.
+ /// Only can instantiate this.
///
internal StorableItemDefinitionBuilderBase(
Func? definitionFactory = null)
@@ -119,7 +119,7 @@ internal StorableItemDefinitionBuilderBase(
///
/// INTERNAL: Creates a builder instance initialized by cloning an existing item.
- /// Only ItemCreator can instantiate this.
+ /// Only can instantiate this.
///
/// The existing item definition to clone properties from.
/// Optional factory function to create the definition instance. If null, a default StorableItemDefinition will be created.
@@ -406,10 +406,10 @@ internal virtual S1ItemFramework.StorableItemDefinition BuildInternal()
///
/// The item definition to wrap.
/// >A wrapper around the given item definition.
- protected virtual Storable.StorableItemDefinition CreateWrapper(
+ protected virtual StorableItemDefinition CreateWrapper(
S1ItemFramework.StorableItemDefinition definition)
{
- return new Storable.StorableItemDefinition(definition);
+ return new StorableItemDefinition(definition);
}
private static S1StationFramework.StationItem GetOrCreateStationItemPrefab(
diff --git a/S1API/Items/StorableItemDefinitionBuilder.cs b/S1API/Items/StorableItemDefinitionBuilder.cs
index 0a1cffb..0ae3486 100644
--- a/S1API/Items/StorableItemDefinitionBuilder.cs
+++ b/S1API/Items/StorableItemDefinitionBuilder.cs
@@ -30,7 +30,7 @@ namespace S1API.Items
/// All items in Schedule One are StorableItemDefinition (or subclasses thereof).
/// The base ItemDefinition class is never used directly in the game.
///
- [Obsolete("Use S1API.Items.ItemBuilders.StorableItemDefinitionBuilder instead")]
+ [Obsolete("Use S1API.Items.Storable.StorableItemDefinitionBuilder instead")]
public sealed class StorableItemDefinitionBuilder
{
private static readonly Log Logger = new Log("StorableItemDefinitionBuilder");
From c69d01f109fa8317eca48c5f3031ef8f5eaab425 Mon Sep 17 00:00:00 2001
From: k0 <21180271+k073l@users.noreply.github.com>
Date: Thu, 4 Jun 2026 13:59:20 +0200
Subject: [PATCH 5/7] fix(items): guard against null in builders
---
S1API/Items/BuildableItemDefinitionBuilder.cs | 2 ++
S1API/Items/ClothingItemDefinitionBuilder.cs | 2 ++
S1API/Items/QualityItemDefinitionBuilder.cs | 1 +
S1API/Items/Storable/StorableItemDefinitionBuilder.cs | 5 +++++
S1API/Items/StorableItemDefinitionBuilder.cs | 1 +
5 files changed, 11 insertions(+)
diff --git a/S1API/Items/BuildableItemDefinitionBuilder.cs b/S1API/Items/BuildableItemDefinitionBuilder.cs
index 1ef4450..fa31a42 100644
--- a/S1API/Items/BuildableItemDefinitionBuilder.cs
+++ b/S1API/Items/BuildableItemDefinitionBuilder.cs
@@ -191,6 +191,8 @@ public BuildableItemDefinitionBuilder WithEquippable(Equippable equippable)
/// A wrapper around the created buildable item definition.
public BuildableItemDefinition Build()
{
+ if (string.IsNullOrEmpty(_definition.ID)) throw new ArgumentNullException(nameof(_definition.ID));
+
// Register with the game's registry
S1Registry.Instance.AddToRegistry(_definition);
diff --git a/S1API/Items/ClothingItemDefinitionBuilder.cs b/S1API/Items/ClothingItemDefinitionBuilder.cs
index 1a413ce..0f71c2e 100644
--- a/S1API/Items/ClothingItemDefinitionBuilder.cs
+++ b/S1API/Items/ClothingItemDefinitionBuilder.cs
@@ -239,6 +239,8 @@ public ClothingItemDefinitionBuilder WithPricing(float basePurchasePrice, float
/// A wrapper around the created clothing item definition.
public ClothingItemDefinition Build()
{
+ if (string.IsNullOrEmpty(_definition.ID)) throw new ArgumentNullException(nameof(_definition.ID));
+
EnsureNativeClothingItemUi();
// Register with the game's registry
diff --git a/S1API/Items/QualityItemDefinitionBuilder.cs b/S1API/Items/QualityItemDefinitionBuilder.cs
index 55f1369..937ec72 100644
--- a/S1API/Items/QualityItemDefinitionBuilder.cs
+++ b/S1API/Items/QualityItemDefinitionBuilder.cs
@@ -275,6 +275,7 @@ public QualityItemDefinitionBuilder WithDefaultQuality(Products.Quality quality)
/// A wrapper around the created storable item definition.
public QualityItemDefinition Build()
{
+ if (string.IsNullOrEmpty(_definition.ID)) throw new ArgumentNullException(nameof(_definition.ID));
if (!_hasCustomStoredItem && _definition.StoredItem != null)
{
// Ensure placeholder naming stays in sync after late changes.
diff --git a/S1API/Items/Storable/StorableItemDefinitionBuilder.cs b/S1API/Items/Storable/StorableItemDefinitionBuilder.cs
index d641335..fcfe484 100644
--- a/S1API/Items/Storable/StorableItemDefinitionBuilder.cs
+++ b/S1API/Items/Storable/StorableItemDefinitionBuilder.cs
@@ -372,6 +372,11 @@ public TSelf WithRequiredRank(Leveling.FullRank? rank)
///
protected virtual Storable.StorableItemDefinition Build()
{
+ if (string.IsNullOrEmpty(Definition.ID))
+ {
+ Logger.Error("Cannot build item definition: ID is required. Use WithBasicInfo(...) to set the ID.");
+ throw new ArgumentNullException(Definition.ID);
+ }
if (!_hasCustomStoredItem && Definition.StoredItem != null)
{
// Ensure placeholder naming stays in sync after late changes.
diff --git a/S1API/Items/StorableItemDefinitionBuilder.cs b/S1API/Items/StorableItemDefinitionBuilder.cs
index 0ae3486..3627296 100644
--- a/S1API/Items/StorableItemDefinitionBuilder.cs
+++ b/S1API/Items/StorableItemDefinitionBuilder.cs
@@ -267,6 +267,7 @@ public StorableItemDefinitionBuilder WithRequiredRank(Leveling.FullRank? rank)
/// A wrapper around the created storable item definition.
public StorableItemDefinition Build()
{
+ if (string.IsNullOrEmpty(_definition.ID)) throw new ArgumentNullException(nameof(_definition.ID));
if (!_hasCustomStoredItem && _definition.StoredItem != null)
{
// Ensure placeholder naming stays in sync after late changes.
From 477f459de9d38489764e3ee32abc6c4ddf552b30 Mon Sep 17 00:00:00 2001
From: k0 <21180271+k073l@users.noreply.github.com>
Date: Thu, 4 Jun 2026 14:01:17 +0200
Subject: [PATCH 6/7] refactor(items): remove unused requiredRank parameter
---
S1API/Items/ItemCreator.cs | 4 +---
S1API/Items/Storable/ItemCreator.cs | 4 +---
2 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/S1API/Items/ItemCreator.cs b/S1API/Items/ItemCreator.cs
index b8b171f..2991f12 100644
--- a/S1API/Items/ItemCreator.cs
+++ b/S1API/Items/ItemCreator.cs
@@ -91,8 +91,7 @@ public static StorableItemDefinitionBuilder CloneFrom(StorableItemDefinition sou
/// Base price when buying from shops (default: 10).
/// Fraction of purchase price recovered when selling (default: 0.5).
/// Whether the item is legal or illegal (default: Legal).
- /// Whether purchasing the item requires a certain player rank (default: false).
- /// The player rank required to purchase the item, if applicable (default: null).
+ /// The player rank required to purchase the item, null if no rank required (default: null).
/// Optional sprite to use as the item icon.
/// Optional equippable component to attach.
/// A wrapper around the created item definition.
@@ -117,7 +116,6 @@ public static StorableItemDefinition CreateItem(
float basePurchasePrice = 10f,
float resellMultiplier = 0.5f,
LegalStatus legalStatus = LegalStatus.Legal,
- bool requiresLevelToPurchase = false,
FullRank? requiredRank = null,
Sprite icon = null,
Equippable equippable = null)
diff --git a/S1API/Items/Storable/ItemCreator.cs b/S1API/Items/Storable/ItemCreator.cs
index 202ffac..f00708d 100644
--- a/S1API/Items/Storable/ItemCreator.cs
+++ b/S1API/Items/Storable/ItemCreator.cs
@@ -90,8 +90,7 @@ public static StorableItemDefinitionBuilder CloneFrom(StorableItemDefinition sou
/// Base price when buying from shops (default: 10).
/// Fraction of purchase price recovered when selling (default: 0.5).
/// Whether the item is legal or illegal (default: Legal).
- /// Whether purchasing the item requires a certain player rank (default: false).
- /// The player rank required to purchase the item, if applicable (default: null).
+ /// The player rank required to purchase the item, null if no rank required (default: null).
/// Optional sprite to use as the item icon.
/// Optional equippable component to attach.
/// A wrapper around the created item definition.
@@ -116,7 +115,6 @@ public static StorableItemDefinition CreateItem(
float basePurchasePrice = 10f,
float resellMultiplier = 0.5f,
LegalStatus legalStatus = LegalStatus.Legal,
- bool requiresLevelToPurchase = false,
FullRank? requiredRank = null,
Sprite icon = null,
Equippable equippable = null)
From 2b3efd0103109178fc7f4d52fa5306980a3d1f18 Mon Sep 17 00:00:00 2001
From: k0 <21180271+k073l@users.noreply.github.com>
Date: Thu, 4 Jun 2026 16:03:55 +0200
Subject: [PATCH 7/7] fix(items): validate ID before lookup
---
S1API/Items/Additive/AdditiveDefinitionBuilder.cs | 1 +
S1API/Items/Additive/AdditiveItemCreator.cs | 5 +++++
S1API/Items/Buildable/BuildableItemCreator.cs | 7 ++++++-
S1API/Items/Buildable/BuildableItemDefinitionBuilder.cs | 2 +-
S1API/Items/BuildableItemDefinitionBuilder.cs | 3 ++-
S1API/Items/Clothing/ClothingItemCreator.cs | 5 +++++
S1API/Items/ClothingItemDefinitionBuilder.cs | 3 ++-
S1API/Items/ItemManager.cs | 6 +++---
S1API/Items/Quality/QualityItemCreator.cs | 5 +++++
S1API/Items/QualityItemDefinitionBuilder.cs | 3 ++-
S1API/Items/Storable/ItemCreator.cs | 5 +++++
S1API/Items/Storable/StorableItemDefinitionBuilder.cs | 4 ++--
S1API/Items/StorableItemDefinitionBuilder.cs | 3 ++-
13 files changed, 41 insertions(+), 11 deletions(-)
diff --git a/S1API/Items/Additive/AdditiveDefinitionBuilder.cs b/S1API/Items/Additive/AdditiveDefinitionBuilder.cs
index 3fe9449..e8bf14f 100644
--- a/S1API/Items/Additive/AdditiveDefinitionBuilder.cs
+++ b/S1API/Items/Additive/AdditiveDefinitionBuilder.cs
@@ -83,6 +83,7 @@ public AdditiveDefinitionBuilder WithDisplayMaterial(Material material)
///
public AdditiveDefinitionBuilder WithEffects(float yieldMultiplier, float instantGrowth, float qualityChange)
{
+ instantGrowth = Mathf.Clamp01(instantGrowth);
if (!AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.YieldMultiplier),
yieldMultiplier))
{
diff --git a/S1API/Items/Additive/AdditiveItemCreator.cs b/S1API/Items/Additive/AdditiveItemCreator.cs
index 4467077..98c6893 100644
--- a/S1API/Items/Additive/AdditiveItemCreator.cs
+++ b/S1API/Items/Additive/AdditiveItemCreator.cs
@@ -33,6 +33,11 @@ public static AdditiveDefinitionBuilder CreateBuilder()
/// Thrown if the source item does not exist or is not an additive.
public static AdditiveDefinitionBuilder CloneFrom(string sourceItemId)
{
+ if (string.IsNullOrWhiteSpace(sourceItemId))
+ {
+ throw new ArgumentException("Source item ID cannot be null or whitespace", nameof(sourceItemId));
+ }
+
var sourceDefinition = S1Registry.GetItem(sourceItemId);
if (sourceDefinition == null)
{
diff --git a/S1API/Items/Buildable/BuildableItemCreator.cs b/S1API/Items/Buildable/BuildableItemCreator.cs
index 66055b2..65b7a69 100644
--- a/S1API/Items/Buildable/BuildableItemCreator.cs
+++ b/S1API/Items/Buildable/BuildableItemCreator.cs
@@ -55,6 +55,11 @@ public static BuildableItemDefinitionBuilder CreateBuilder()
///
public static BuildableItemDefinitionBuilder CloneFrom(string sourceItemId)
{
+ if (string.IsNullOrWhiteSpace(sourceItemId))
+ {
+ throw new ArgumentException("Source item ID cannot be null or whitespace", nameof(sourceItemId));
+ }
+
var sourceDefinition = S1Registry.GetItem(sourceItemId);
if (sourceDefinition == null)
@@ -84,7 +89,7 @@ public static BuildableItemDefinitionBuilder CloneFrom(string sourceItemId)
/// A builder initialized with the source item's properties, ready for customization.
///
///
- /// var originalRack = ItemManager.GetItemDefinition("StorageRack-1x0.5") as BuildableItemDefinition;
+ /// var originalRack = ItemManager.GetDefinition("StorageRack-1x0.5") as BuildableItemDefinition;
/// var metalRack = BuildableItemCreator.CloneFrom(originalRack)
/// .WithBasicInfo("metal_rack_small", "Small Metal Storage Rack", "A metal version")
/// .WithBuildSound(BuildSoundType.Metal)
diff --git a/S1API/Items/Buildable/BuildableItemDefinitionBuilder.cs b/S1API/Items/Buildable/BuildableItemDefinitionBuilder.cs
index 87de62d..b01f562 100644
--- a/S1API/Items/Buildable/BuildableItemDefinitionBuilder.cs
+++ b/S1API/Items/Buildable/BuildableItemDefinitionBuilder.cs
@@ -56,7 +56,7 @@ protected override void CopyPropertiesFrom(S1ItemFramework.StorableItemDefinitio
///
/// The build sound type.
/// The builder instance for fluent chaining.
- public BuildableItemDefinitionBuilder WithBuildSound(Items.BuildSoundType soundType)
+ public BuildableItemDefinitionBuilder WithBuildSound(BuildSoundType soundType)
{
BuildableDefinition.BuildSoundType = (S1ItemFramework.BuildableItemDefinition.EBuildSoundType)soundType;
return this;
diff --git a/S1API/Items/BuildableItemDefinitionBuilder.cs b/S1API/Items/BuildableItemDefinitionBuilder.cs
index fa31a42..07eaa93 100644
--- a/S1API/Items/BuildableItemDefinitionBuilder.cs
+++ b/S1API/Items/BuildableItemDefinitionBuilder.cs
@@ -191,7 +191,8 @@ public BuildableItemDefinitionBuilder WithEquippable(Equippable equippable)
/// A wrapper around the created buildable item definition.
public BuildableItemDefinition Build()
{
- if (string.IsNullOrEmpty(_definition.ID)) throw new ArgumentNullException(nameof(_definition.ID));
+ if (string.IsNullOrWhiteSpace(_definition.ID))
+ throw new ArgumentException("Item ID cannot be null, empty, or whitespace.", nameof(_definition.ID));
// Register with the game's registry
S1Registry.Instance.AddToRegistry(_definition);
diff --git a/S1API/Items/Clothing/ClothingItemCreator.cs b/S1API/Items/Clothing/ClothingItemCreator.cs
index 4bbec74..44c6a81 100644
--- a/S1API/Items/Clothing/ClothingItemCreator.cs
+++ b/S1API/Items/Clothing/ClothingItemCreator.cs
@@ -59,6 +59,11 @@ public static ClothingItemDefinitionBuilder CreateBuilder()
///
public static ClothingItemDefinitionBuilder CloneFrom(string sourceItemId)
{
+ if (string.IsNullOrWhiteSpace(sourceItemId))
+ {
+ throw new ArgumentException("Source item ID cannot be null or whitespace", nameof(sourceItemId));
+ }
+
var sourceDefinition = S1.Registry.GetItem(sourceItemId);
if (sourceDefinition == null)
{
diff --git a/S1API/Items/ClothingItemDefinitionBuilder.cs b/S1API/Items/ClothingItemDefinitionBuilder.cs
index 0f71c2e..4ccf82a 100644
--- a/S1API/Items/ClothingItemDefinitionBuilder.cs
+++ b/S1API/Items/ClothingItemDefinitionBuilder.cs
@@ -239,7 +239,8 @@ public ClothingItemDefinitionBuilder WithPricing(float basePurchasePrice, float
/// A wrapper around the created clothing item definition.
public ClothingItemDefinition Build()
{
- if (string.IsNullOrEmpty(_definition.ID)) throw new ArgumentNullException(nameof(_definition.ID));
+ if (string.IsNullOrWhiteSpace(_definition.ID))
+ throw new ArgumentException("Item ID cannot be null, empty, or whitespace.", nameof(_definition.ID));
EnsureNativeClothingItemUi();
diff --git a/S1API/Items/ItemManager.cs b/S1API/Items/ItemManager.cs
index 44e2429..cc6ab96 100644
--- a/S1API/Items/ItemManager.cs
+++ b/S1API/Items/ItemManager.cs
@@ -208,7 +208,7 @@ public static bool UnregisterItem(string itemID)
return false;
}
- ItemDefinition definition = GetItemDefinition(itemID);
+ var definition = GetDefinition(itemID);
if (definition == null)
{
return false;
@@ -216,7 +216,7 @@ public static bool UnregisterItem(string itemID)
RemoveFromRuntimeCleanupQueue(definition.S1ItemDefinition, definition.ID);
S1Registry.Instance.RemoveFromRegistry(definition.S1ItemDefinition);
- return GetItemDefinition(itemID) == null;
+ return GetDefinition(itemID) == null;
}
///
@@ -255,7 +255,7 @@ public static System.Collections.Generic.List GetAllItemDefiniti
continue;
// Use GetItemDefinition to properly wrap the item with the correct type
- var wrappedItem = GetItemDefinition(itemId);
+ var wrappedItem = GetDefinition(itemId);
if (wrappedItem != null)
{
wrappedItems.Add(wrappedItem);
diff --git a/S1API/Items/Quality/QualityItemCreator.cs b/S1API/Items/Quality/QualityItemCreator.cs
index e0fd3e8..73fe4d3 100644
--- a/S1API/Items/Quality/QualityItemCreator.cs
+++ b/S1API/Items/Quality/QualityItemCreator.cs
@@ -34,6 +34,11 @@ public static QualityItemDefinitionBuilder CreateBuilder()
/// Thrown if the source item ID is not found or is not a quality item.
public static QualityItemDefinitionBuilder CloneFrom(string sourceItemId)
{
+ if (string.IsNullOrWhiteSpace(sourceItemId))
+ {
+ throw new ArgumentException("Source item ID cannot be null or whitespace", nameof(sourceItemId));
+ }
+
var sourceDefinition = S1Registry.GetItem(sourceItemId);
if (sourceDefinition == null)
{
diff --git a/S1API/Items/QualityItemDefinitionBuilder.cs b/S1API/Items/QualityItemDefinitionBuilder.cs
index 937ec72..bfb912e 100644
--- a/S1API/Items/QualityItemDefinitionBuilder.cs
+++ b/S1API/Items/QualityItemDefinitionBuilder.cs
@@ -275,7 +275,8 @@ public QualityItemDefinitionBuilder WithDefaultQuality(Products.Quality quality)
/// A wrapper around the created storable item definition.
public QualityItemDefinition Build()
{
- if (string.IsNullOrEmpty(_definition.ID)) throw new ArgumentNullException(nameof(_definition.ID));
+ if (string.IsNullOrWhiteSpace(_definition.ID))
+ throw new ArgumentException("Item ID cannot be null, empty, or whitespace.", nameof(_definition.ID));
if (!_hasCustomStoredItem && _definition.StoredItem != null)
{
// Ensure placeholder naming stays in sync after late changes.
diff --git a/S1API/Items/Storable/ItemCreator.cs b/S1API/Items/Storable/ItemCreator.cs
index f00708d..b93565b 100644
--- a/S1API/Items/Storable/ItemCreator.cs
+++ b/S1API/Items/Storable/ItemCreator.cs
@@ -48,6 +48,11 @@ public static StorableItemDefinitionBuilder CreateBuilder()
/// Thrown if the source item ID is not found or is not a storable item.
public static StorableItemDefinitionBuilder CloneFrom(string sourceItemId)
{
+ if (string.IsNullOrWhiteSpace(sourceItemId))
+ {
+ throw new ArgumentException("Source item ID cannot be null or whitespace", nameof(sourceItemId));
+ }
+
var sourceDefinition = S1Registry.GetItem(sourceItemId);
if (sourceDefinition == null)
{
diff --git a/S1API/Items/Storable/StorableItemDefinitionBuilder.cs b/S1API/Items/Storable/StorableItemDefinitionBuilder.cs
index fcfe484..3d63532 100644
--- a/S1API/Items/Storable/StorableItemDefinitionBuilder.cs
+++ b/S1API/Items/Storable/StorableItemDefinitionBuilder.cs
@@ -372,10 +372,10 @@ public TSelf WithRequiredRank(Leveling.FullRank? rank)
///
protected virtual Storable.StorableItemDefinition Build()
{
- if (string.IsNullOrEmpty(Definition.ID))
+ if (string.IsNullOrWhiteSpace(Definition.ID))
{
Logger.Error("Cannot build item definition: ID is required. Use WithBasicInfo(...) to set the ID.");
- throw new ArgumentNullException(Definition.ID);
+ throw new InvalidOperationException("Cannot build item definition: ID is required.");
}
if (!_hasCustomStoredItem && Definition.StoredItem != null)
{
diff --git a/S1API/Items/StorableItemDefinitionBuilder.cs b/S1API/Items/StorableItemDefinitionBuilder.cs
index 3627296..cf9716c 100644
--- a/S1API/Items/StorableItemDefinitionBuilder.cs
+++ b/S1API/Items/StorableItemDefinitionBuilder.cs
@@ -267,7 +267,8 @@ public StorableItemDefinitionBuilder WithRequiredRank(Leveling.FullRank? rank)
/// A wrapper around the created storable item definition.
public StorableItemDefinition Build()
{
- if (string.IsNullOrEmpty(_definition.ID)) throw new ArgumentNullException(nameof(_definition.ID));
+ if (string.IsNullOrWhiteSpace(_definition.ID))
+ throw new ArgumentException("Item ID cannot be null, empty, or whitespace.", nameof(_definition.ID));
if (!_hasCustomStoredItem && _definition.StoredItem != null)
{
// Ensure placeholder naming stays in sync after late changes.