Skip to content
57 changes: 57 additions & 0 deletions S1API/Items/Additive/AdditiveDefinition.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents an additive item definition.
/// Extends <see cref="StorableItemDefinition"/> with additive-specific properties.
/// </summary>
/// <remarks>
/// Builder-only: these properties are intentionally read-only to avoid runtime surprises from mutating
/// globally-registered ScriptableObject definitions mid-session. Use <see cref="AdditiveItemCreator"/> to create
/// additives with configured effects.
/// </remarks>
public sealed class AdditiveDefinition : Storable.StorableItemDefinition
{
/// <summary>
/// INTERNAL: Wraps an existing native additive definition.
/// </summary>
internal AdditiveDefinition(S1ItemFramework.AdditiveDefinition definition)
: base(definition)
{
S1AdditiveDefinition = definition;
}

/// <summary>
/// INTERNAL: A reference to the native game additive definition.
/// </summary>
internal S1ItemFramework.AdditiveDefinition S1AdditiveDefinition { get; }

/// <summary>
/// Display material used for the additive (if applicable).
/// </summary>
public Material DisplayMaterial => S1AdditiveDefinition.DisplayMaterial;

/// <summary>
/// Quality modifier applied by this additive.
/// </summary>
public float QualityChange => S1AdditiveDefinition.QualityChange;

/// <summary>
/// Yield multiplier applied by this additive.
/// </summary>
public float YieldMultiplier => S1AdditiveDefinition.YieldMultiplier;

/// <summary>
/// Instant growth fraction applied by this additive (0..1).
/// </summary>
public float InstantGrowth => S1AdditiveDefinition.InstantGrowth;
}
}

136 changes: 136 additions & 0 deletions S1API/Items/Additive/AdditiveDefinitionBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#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 S1API.Internal.Utils;
using S1API.Items.Storable;
using S1API.Logging;
using UnityEngine;

namespace S1API.Items.Additive
{
/// <summary>
/// Builder for composing additive definitions at runtime.
/// Use fluent methods to configure additive properties before calling <see cref="Build"/>.
/// </summary>
public sealed class AdditiveDefinitionBuilder
: StorableItemDefinitionBuilderBase<AdditiveDefinitionBuilder>
{
private static readonly Log Logger = new Log("AdditiveDefinitionBuilder");

private S1ItemFramework.AdditiveDefinition AdditiveDefinition =>
CrossType.As<S1ItemFramework.AdditiveDefinition>(Definition);

/// <summary>
/// INTERNAL: Creates a new builder instance with a fresh AdditiveDefinition.
/// Only <see cref="AdditiveItemCreator"/> can instantiate this.
/// </summary>
internal AdditiveDefinitionBuilder()
: base(ScriptableObject.CreateInstance<S1ItemFramework.AdditiveDefinition>)
{
Definition.Category = S1CoreItemFramework.EItemCategory.Agriculture;
}

/// <summary>
/// INTERNAL: Creates a builder instance initialized by cloning an existing additive.
/// </summary>
internal AdditiveDefinitionBuilder(
S1ItemFramework.AdditiveDefinition source)
: base(source,
ScriptableObject.CreateInstance<S1ItemFramework.AdditiveDefinition>)
{
}

/// <inheritdoc/>
protected override void CopyPropertiesFrom(
S1ItemFramework.StorableItemDefinition source)
{
base.CopyPropertiesFrom(source);

var additiveSource = CrossType.As<S1ItemFramework.AdditiveDefinition>(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);
}

/// <summary>
/// Sets the display material for this additive.
/// </summary>
public AdditiveDefinitionBuilder WithDisplayMaterial(Material material)
{
if (!AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.DisplayMaterial),
material))
{
Logger.Warning(
$"Failed to set DisplayMaterial on AdditiveDefinition '{AdditiveDefinition.ID ?? "<no id>"}'.");
}

return this;
}

/// <summary>
/// Sets the effect values for this additive.
/// </summary>
public AdditiveDefinitionBuilder WithEffects(float yieldMultiplier, float instantGrowth, float qualityChange)
{
instantGrowth = Mathf.Clamp01(instantGrowth);
if (!AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.YieldMultiplier),
yieldMultiplier))
{
Logger.Warning(
$"Failed to set YieldMultiplier on AdditiveDefinition '{AdditiveDefinition.ID ?? "<no id>"}'.");
}

if (!AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.InstantGrowth),
instantGrowth))
{
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Logger.Warning(
$"Failed to set InstantGrowth on AdditiveDefinition '{AdditiveDefinition.ID ?? "<no id>"}'.");
}

if (!AutoPropertySetter.TrySet(AdditiveDefinition, nameof(S1ItemFramework.AdditiveDefinition.QualityChange),
qualityChange))
{
Logger.Warning(
$"Failed to set QualityChange on AdditiveDefinition '{AdditiveDefinition.ID ?? "<no id>"}'.");
}

return this;
}

/// <summary>
/// Builds the item definition, registers it with the game's registry, and returns a wrapper.
/// </summary>
/// <returns>A wrapper around the created additive definition.</returns>
public new AdditiveDefinition Build()
{
return (AdditiveDefinition)base.Build();
}

/// <summary>
/// INTERNAL: Builds and returns the raw game item definition without registering.
/// Used internally by S1API. Modders should use <see cref="Build"/> instead.
/// </summary>
internal new S1ItemFramework.AdditiveDefinition BuildInternal()
{
return AdditiveDefinition;
}

/// <inheritdoc />
protected override Storable.StorableItemDefinition CreateWrapper(
S1ItemFramework.StorableItemDefinition definition)
{
return new AdditiveDefinition(CrossType.As<S1ItemFramework.AdditiveDefinition>(definition));
}
}
}
71 changes: 71 additions & 0 deletions S1API/Items/Additive/AdditiveItemCreator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#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
{
/// <summary>
/// Provides convenient static methods for creating custom additive items.
/// Use <see cref="CreateBuilder"/> for creating additives from scratch, or <see cref="CloneFrom"/> for variants.
/// </summary>
public static class AdditiveItemCreator
{
/// <summary>
/// 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.
/// </summary>
public static AdditiveDefinitionBuilder CreateBuilder()
{
return new AdditiveDefinitionBuilder();
}

/// <summary>
/// Creates a new additive builder by cloning an existing additive by ID.
/// </summary>
/// <param name="sourceItemId">The ID of the additive to clone.</param>
/// <returns>A builder pre-configured with the source additive's properties.</returns>
/// <exception cref="ArgumentException">Thrown if the source item does not exist or is not an additive.</exception>
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)
{
throw new ArgumentException($"Source item with ID '{sourceItemId}' not found in registry", nameof(sourceItemId));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

if (!CrossType.Is(sourceDefinition, out S1ItemFramework.AdditiveDefinition additiveDef))
{
throw new ArgumentException($"Item '{sourceItemId}' is not an AdditiveDefinition", nameof(sourceItemId));
}

return new AdditiveDefinitionBuilder(additiveDef);
}

/// <summary>
/// Creates a new additive builder by cloning an existing additive wrapper.
/// </summary>
/// <param name="source">The additive definition to clone from.</param>
/// <returns>A builder pre-configured with the source additive's properties.</returns>
public static AdditiveDefinitionBuilder CloneFrom(AdditiveDefinition source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source), "Source additive definition cannot be null");
}

return new AdditiveDefinitionBuilder(source.S1AdditiveDefinition);
}
}
}

2 changes: 2 additions & 0 deletions S1API/Items/AdditiveDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif

using System;
using UnityEngine;

namespace S1API.Items
Expand All @@ -17,6 +18,7 @@ namespace S1API.Items
/// globally-registered ScriptableObject definitions mid-session. Use <see cref="AdditiveItemCreator"/> to create
/// additives with configured effects.
/// </remarks>
[Obsolete("Use S1API.Items.Additive.AdditiveDefinition instead")]
public sealed class AdditiveDefinition : StorableItemDefinition
{
/// <summary>
Expand Down
1 change: 1 addition & 0 deletions S1API/Items/AdditiveDefinitionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace S1API.Items
/// Builder for composing additive definitions at runtime.
/// Use fluent methods to configure additive properties before calling <see cref="Build"/>.
/// </summary>
[Obsolete("Use S1API.Items.Additive.AdditiveDefinitionBuilder instead")]
public sealed class AdditiveDefinitionBuilder
{
private static readonly Log Logger = new Log("AdditiveDefinitionBuilder");
Expand Down
1 change: 1 addition & 0 deletions S1API/Items/AdditiveItemCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace S1API.Items
/// Provides convenient static methods for creating custom additive items.
/// Use <see cref="CreateBuilder"/> for creating additives from scratch, or <see cref="CloneFrom"/> for variants.
/// </summary>
[Obsolete("Use S1API.Items.Additive.AdditiveItemCreator instead")]
public static class AdditiveItemCreator
{
/// <summary>
Expand Down
Loading
Loading