diff --git a/S1API/Entities/NPC.cs b/S1API/Entities/NPC.cs
index a758395f..6c24deac 100644
--- a/S1API/Entities/NPC.cs
+++ b/S1API/Entities/NPC.cs
@@ -592,7 +592,7 @@ public bool ConversationCanBeHidden
///
/// The NPC class to get the instance of.
///
- public static NPC? Get() =>
+ public static NPC? Get() where T : NPC =>
All.FirstOrDefault(npc => npc.GetType() == typeof(T));
#endregion
diff --git a/S1API/Internal/Patches/QuestPatches.cs b/S1API/Internal/Patches/QuestPatches.cs
index b5e062dc..bc7d1e6a 100644
--- a/S1API/Internal/Patches/QuestPatches.cs
+++ b/S1API/Internal/Patches/QuestPatches.cs
@@ -1,13 +1,14 @@
-#if (IL2CPPMELON)
+#if (IL2CPPMELON)
using S1Loaders = Il2CppScheduleOne.Persistence.Loaders;
using S1Datas = Il2CppScheduleOne.Persistence.Datas;
using S1Quests = Il2CppScheduleOne.Quests;
+using S1Persistence = Il2CppScheduleOne.Persistence;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1Loaders = ScheduleOne.Persistence.Loaders;
using S1Datas = ScheduleOne.Persistence.Datas;
using S1Quests = ScheduleOne.Quests;
+using S1Persistence = ScheduleOne.Persistence;
#endif
-
#if (IL2CPPMELON || IL2CPPBEPINEX)
using Il2CppSystem.Collections.Generic;
#elif (MONOMELON || MONOBEPINEX)
@@ -19,63 +20,108 @@
using System.Linq;
using HarmonyLib;
using Newtonsoft.Json;
-using S1API.Internal.Abstraction;
using S1API.Internal.Utils;
using S1API.Quests;
using UnityEngine;
+using ISaveable = S1API.Internal.Abstraction.ISaveable;
namespace S1API.Internal.Patches
{
///
- /// INTERNAL: All patches related to quests.
+ /// INTERNAL: Contains patches related to quest processing and custom modifications.
///
[HarmonyPatch]
internal class QuestPatches
{
///
- /// Patching performed when all quests are saved.
+ /// Provides a centralized logging mechanism to capture and output messages, warnings,
+ /// and errors during runtime, using underlying logging frameworks like BepInEx or MelonLoader.
+ ///
+ protected static readonly Logging.Log Logger = new Logging.Log("QuestPatches");
+
+ ///
+ /// Executes additional logic after quests are saved by the SaveManager.
+ /// Ensures that directories for modded quests are properly created and that
+ /// only non-vanilla modded quests are saved into the specified folder.
///
- /// Instance of the quest manager.
- /// Path to the base Quest folder.
- /// List of extra saveable data. The game uses this for cleanup later.
- [HarmonyPatch(typeof(S1Quests.QuestManager), "WriteData")]
+ /// The path to the save folder where quests are being stored.
+ [HarmonyPatch(typeof(S1Persistence.SaveManager), nameof(S1Persistence.SaveManager.Save), typeof(string))]
[HarmonyPostfix]
- private static void QuestManagerWriteData(S1Quests.QuestManager __instance, string parentFolderPath, ref List __result)
+ private static void SaveManager_Save_Postfix(string saveFolderPath)
{
- string questsPath = Path.Combine(parentFolderPath, "Quests");
+ try
+ {
+ var saveManager = S1Persistence.SaveManager.Instance;
+
+ string[] approved = {
+ "Modded",
+ Path.Combine("Modded", "Quests")
+ };
+
+ foreach (var path in approved)
+ {
+ if (!saveManager.ApprovedBaseLevelPaths.Contains(path))
+ saveManager.ApprovedBaseLevelPaths.Add(path);
+ }
+
+ // ✅ Create the directory structure
+ string questsPath = Path.Combine(saveFolderPath, "Modded", "Quests");
+ Directory.CreateDirectory(questsPath);
+
+ // ✅ Save only non-vanilla modded quests
+ foreach (Quest quest in QuestManager.Quests)
+ {
+ if (!quest.GetType().Namespace.StartsWith("ScheduleOne"))
+ {
+ List dummy = new List();
+ quest.SaveInternal(questsPath, ref dummy);
+ }
+ }
- foreach (Quest quest in QuestManager.Quests)
- quest.SaveInternal(questsPath, ref __result);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("[S1API] ❌ Failed to save modded quests:\n" + ex);
+ }
}
+
///
- /// Patching performed for when all quests are loaded.
+ /// Invoked after all base quests are loaded to handle modded quest loading.
+ /// Loads modded quests from a specific "Modded/Quests" directory and integrates them into the game.
///
- /// Instance of the quest loader.
- /// Path to the base Quest folder.
+ /// The quest loader instance responsible for managing quest load operations.
+ /// The path to the primary quest directory in the base game.
[HarmonyPatch(typeof(S1Loaders.QuestsLoader), "Load")]
[HarmonyPostfix]
private static void QuestsLoaderLoad(S1Loaders.QuestsLoader __instance, string mainPath)
{
- // Make sure we have a quests directory (fresh saves don't at this point in runtime)
- if (!Directory.Exists(mainPath))
+ string moddedQuestsPath = Path.Combine(
+ S1Persistence.LoadManager.Instance.LoadedGameFolderPath,
+ "Modded", "Quests"
+ );
+
+ if (!Directory.Exists(moddedQuestsPath))
+ {
+ Directory.CreateDirectory(moddedQuestsPath);
return;
+ }
- string[] questDirectories = Directory.GetDirectories(mainPath)
+ string[] questDirectories = Directory.GetDirectories(moddedQuestsPath)
.Select(Path.GetFileName)
.Where(directory => directory != null && directory.StartsWith("Quest_"))
- .ToArray()!;
+ .ToArray();
foreach (string questDirectory in questDirectories)
{
- string baseQuestPath = Path.Combine(mainPath, questDirectory);
+ string baseQuestPath = Path.Combine(moddedQuestsPath, questDirectory);
__instance.TryLoadFile(baseQuestPath, out string questDataText);
if (questDataText == null)
continue;
S1Datas.QuestData baseQuestData = JsonUtility.FromJson(questDataText);
- string questDirectoryPath = Path.Combine(mainPath, questDirectory);
+ string questDirectoryPath = Path.Combine(moddedQuestsPath, questDirectory);
string questDataPath = Path.Combine(questDirectoryPath, "QuestData");
if (!__instance.TryLoadFile(questDataPath, out string questText))
continue;
@@ -93,26 +139,12 @@ private static void QuestsLoaderLoad(S1Loaders.QuestsLoader __instance, string m
}
}
+
///
- /// Patching performed for when stale files are deleted.
+ /// Executes logic prior to the start of a quest.
+ /// Ensures that linked modded quest data is properly initialized.
///
- /// Instance of the quest manager.
- /// Path to the base Quest folder.
- [HarmonyPatch(typeof(S1Quests.QuestManager), "DeleteUnapprovedFiles")]
- [HarmonyPostfix]
- private static void QuestManagerDeleteUnapprovedFiles(S1Quests.QuestManager __instance, string parentFolderPath)
- {
- string questFolder = Path.Combine(parentFolderPath, "Quests");
- string?[] existingQuests = QuestManager.Quests.Select(quest => quest.SaveFolder).ToArray();
-
- string[] unapprovedQuestDirectories = Directory.GetDirectories(questFolder)
- .Where(directory => directory.StartsWith("Quest_") && !existingQuests.Contains(directory))
- .ToArray();
-
- foreach (string unapprovedQuestDirectory in unapprovedQuestDirectories)
- Directory.Delete(unapprovedQuestDirectory, true);
- }
-
+ /// The instance of the quest that is being started.
[HarmonyPatch(typeof(S1Quests.Quest), "Start")]
[HarmonyPrefix]
private static void QuestStart(S1Quests.Quest __instance) =>
diff --git a/S1API/Items/ItemDefinition.cs b/S1API/Items/ItemDefinition.cs
index 24b6e213..7cffc920 100644
--- a/S1API/Items/ItemDefinition.cs
+++ b/S1API/Items/ItemDefinition.cs
@@ -1,4 +1,4 @@
-#if (IL2CPPMELON)
+#if (IL2CPPMELON)
using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1ItemFramework = ScheduleOne.ItemFramework;
@@ -153,16 +153,29 @@ public override bool Equals(object? obj) =>
/// The first to compare.
/// The second to compare.
/// true if both instances are equal or have the same S1ItemDefinition; otherwise, false.
- public static bool operator ==(ItemDefinition? a, ItemDefinition? b) =>
- ReferenceEquals(a, b) || a != null && b != null && a.S1ItemDefinition == b.S1ItemDefinition;
-
+ public static bool operator ==(ItemDefinition? a, ItemDefinition? b)
+ {
+ if (ReferenceEquals(a, b))
+ return true;
+ if (a is null || b is null)
+ return false;
+ return ReferenceEquals(a.S1ItemDefinition, b.S1ItemDefinition);
+ }
///
/// Determines whether two instances are not equal.
///
/// The first to compare.
/// The second to compare.
/// true if the instances are not equal; otherwise, false.
- public static bool operator !=(ItemDefinition? a, ItemDefinition? b) => !(a == b);
+ public static bool operator !=(ItemDefinition? a, ItemDefinition? b)
+ {
+ if (ReferenceEquals(a, b))
+ return false;
+ if (a is null || b is null)
+ return true;
+ return !ReferenceEquals(a.S1ItemDefinition, b.S1ItemDefinition);
+ }
+
}
///
diff --git a/S1API/Products/CocaineDefinition.cs b/S1API/Products/CocaineDefinition.cs
index 24feb99f..0ee5e2f5 100644
--- a/S1API/Products/CocaineDefinition.cs
+++ b/S1API/Products/CocaineDefinition.cs
@@ -1,39 +1,61 @@
#if (IL2CPPMELON)
using Il2CppScheduleOne.Product;
using S1CocaineDefinition = Il2CppScheduleOne.Product.CocaineDefinition;
+using S1Properties = Il2CppScheduleOne.Properties;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using ScheduleOne.Product;
using S1CocaineDefinition = ScheduleOne.Product.CocaineDefinition;
+using S1Properties = ScheduleOne.Properties;
#endif
+using System.Collections.Generic;
using S1API.Internal.Utils;
using S1API.Items;
namespace S1API.Products
{
///
- /// Represents the definition of a Cocaine product.
+ /// Defines the characteristics and behaviors of a cocaine product within the system.
///
public class CocaineDefinition : ProductDefinition
{
///
- /// INTERNAL: Strongly typed access to the CocaineDefinition.
+ /// Provides internal access to the CocaineDefinition type within the Schedule One system.
///
internal S1CocaineDefinition S1CocaineDefinition =>
CrossType.As(S1ItemDefinition);
///
- /// Creates a new cocaine product definition.
+ /// Represents the definition of a cocaine product within the system.
///
- /// The original in-game cocaine definition.
internal CocaineDefinition(S1CocaineDefinition definition)
- : base(definition) { }
+ : base(definition)
+ {
+ }
///
- /// Creates an instance of this cocaine product.
+ /// Creates an instance of this product definition with the specified quantity.
///
+ /// The quantity of the product to instantiate. Defaults to 1 if not specified.
+ /// An representing the instantiated product with the specified quantity.
public override ItemInstance CreateInstance(int quantity = 1) =>
new ProductInstance(CrossType.As(
S1CocaineDefinition.GetDefaultInstance(quantity)));
+
+ ///
+ /// Retrieves a list of properties associated with this product definition.
+ ///
+ /// A list of properties for the product.
+ public List GetProperties()
+ {
+ var result = new List();
+ var list = S1CocaineDefinition?.Properties;
+ if (list != null)
+ {
+ for (int i = 0; i < list.Count; i++)
+ result.Add(list[i]);
+ }
+ return result;
+ }
}
}
diff --git a/S1API/Products/MethDefinition.cs b/S1API/Products/MethDefinition.cs
index ee8e16d1..87689244 100644
--- a/S1API/Products/MethDefinition.cs
+++ b/S1API/Products/MethDefinition.cs
@@ -1,39 +1,60 @@
#if (IL2CPPMELON)
using Il2CppScheduleOne.Product;
using S1MethDefinition = Il2CppScheduleOne.Product.MethDefinition;
+using S1Properties = Il2CppScheduleOne.Properties;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using ScheduleOne.Product;
using S1MethDefinition = ScheduleOne.Product.MethDefinition;
+using S1Properties = ScheduleOne.Properties;
#endif
+using System.Collections.Generic;
using S1API.Internal.Utils;
using S1API.Items;
-
namespace S1API.Products
{
///
- /// Represents the definition of a Meth product.
+ /// Represents the definition of a meth product within the ScheduleOne product framework.
///
public class MethDefinition : ProductDefinition
{
///
- /// INTERNAL: Strongly typed access to the MethDefinition.
+ /// INTERNAL: Strongly typed access to S1MethDefinition.
///
internal S1MethDefinition S1MethDefinition =>
CrossType.As(S1ItemDefinition);
///
- /// Creates a new meth product definition.
+ /// Represents the definition of a Meth product in the product framework.
///
- /// The original in-game meth definition.
internal MethDefinition(S1MethDefinition definition)
- : base(definition) { }
+ : base(definition)
+ {
+ }
///
- /// Creates an instance of this meth product.
+ /// Creates an instance of this meth product with the specified quantity.
///
+ /// The quantity of the product instance to create. Defaults to 1 if not specified.
+ /// An instance of representing the created meth product.
public override ItemInstance CreateInstance(int quantity = 1) =>
new ProductInstance(CrossType.As(
S1MethDefinition.GetDefaultInstance(quantity)));
+
+ ///
+ /// Retrieves the list of properties associated with the meth product definition.
+ ///
+ /// A list of properties defined for the meth product.
+ public List GetProperties()
+ {
+ var result = new List();
+ var list = S1MethDefinition?.Properties;
+ if (list != null)
+ {
+ for (int i = 0; i < list.Count; i++)
+ result.Add(list[i]);
+ }
+ return result;
+ }
}
}
diff --git a/S1API/Products/ProductDefinition.cs b/S1API/Products/ProductDefinition.cs
index 09751899..7ba72fb6 100644
--- a/S1API/Products/ProductDefinition.cs
+++ b/S1API/Products/ProductDefinition.cs
@@ -1,10 +1,15 @@
#if (IL2CPPMELON)
using Il2CppInterop.Runtime.InteropTypes;
using S1Product = Il2CppScheduleOne.Product;
+using ItemFramework = Il2CppScheduleOne.ItemFramework;
+using Properties = Il2CppScheduleOne.Properties;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1Product = ScheduleOne.Product;
+using ItemFramework = ScheduleOne.ItemFramework;
+using Properties = ScheduleOne.Properties;
#endif
+using System.Collections.Generic;
using S1API.Internal.Utils;
using S1API.Items;
using UnityEngine;
@@ -26,8 +31,11 @@ public class ProductDefinition : ItemDefinition
/// INTERNAL: Creates a product definition from the in-game product definition.
///
///
- internal ProductDefinition(S1Product.ProductDefinition productDefinition) : base(productDefinition) { }
-
+#if IL2CPPMELON
+ internal ProductDefinition(ItemFramework.ItemDefinition productDefinition) : base(productDefinition) { }
+#else
+ internal ProductDefinition(ItemFramework.ItemDefinition productDefinition) : base(productDefinition) { }
+#endif
///
/// The price associated with this product.
///
@@ -49,6 +57,14 @@ public Sprite Icon
{
get { return S1ProductDefinition.Icon; }
}
+#if IL2CPPMELON
+ private List properties; // or however properties are stored
+ public List Properties; // or however properties are stored
+#else
+ private List properties; // or however properties are stored
+ public IReadOnlyList Properties => properties.AsReadOnly();
+#endif
- }
+
+}
}
diff --git a/S1API/Products/ProductDefinitionWrapper.cs b/S1API/Products/ProductDefinitionWrapper.cs
index 14e904b8..923744cc 100644
--- a/S1API/Products/ProductDefinitionWrapper.cs
+++ b/S1API/Products/ProductDefinitionWrapper.cs
@@ -1,27 +1,34 @@
using S1API.Internal.Utils;
-
#if (IL2CPPMELON)
using S1Product = Il2CppScheduleOne.Product;
-#else
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1Product = ScheduleOne.Product;
#endif
namespace S1API.Products
{
///
- /// INTERNAL: A wrapper class for converting a product definition to its proper dedicated class.
+ /// Provides functionality to wrap and convert generic product definitions into their specific type-derived definitions.
///
- internal static class ProductDefinitionWrapper
+ public static class ProductDefinitionWrapper
{
- internal static ProductDefinition Wrap(ProductDefinition def)
+ ///
+ /// Converts a generic into its corresponding typed wrapper.
+ ///
+ /// The raw product definition to be processed and converted.
+ /// A wrapped instance of with type-specific methods and properties, or the input definition if no specific wrapper applies.
+ public static ProductDefinition Wrap(ProductDefinition def)
{
var item = def.S1ItemDefinition;
if (CrossType.Is(item, out var weed))
return new WeedDefinition(weed);
+
if (CrossType.Is(item, out var meth))
return new MethDefinition(meth);
+
if (CrossType.Is(item, out var coke))
return new CocaineDefinition(coke);
+
return def;
}
}
diff --git a/S1API/Products/ProductInstance.cs b/S1API/Products/ProductInstance.cs
index f3fd7f4b..150c6e72 100644
--- a/S1API/Products/ProductInstance.cs
+++ b/S1API/Products/ProductInstance.cs
@@ -1,41 +1,72 @@
-#if (IL2CPPMELON)
+#if (IL2CPPMELON )
using S1Product = Il2CppScheduleOne.Product;
+using S1Properties = Il2CppScheduleOne.Properties;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1Product = ScheduleOne.Product;
+using S1Properties = ScheduleOne.Properties;
#endif
-
+using System.Collections.Generic;
using S1API.Internal.Utils;
-using S1API.Items;
-
+using S1ItemInstance = S1API.Items.ItemInstance;
namespace S1API.Products
{
///
/// Represents an instance of a product in the game.
///
- public class ProductInstance : ItemInstance
+ ///
+ /// This class defines specific properties and behaviors for a product instance,
+ /// such as quality, packaging, and definition, derived from the S1API's item instance structure.
+ ///
+ public class ProductInstance : S1ItemInstance
{
///
- /// INTERNAL: The stored reference to the in-game product instance.
+ /// INTERNAL: Provides access to the underlying in-game product item instance.
///
internal S1Product.ProductItemInstance S1ProductInstance =>
CrossType.As(S1ItemInstance);
///
- /// INTERNAL: Creates a product instance from the in-game product instance.
+ /// Represents an instance of a product, derived from a specific in-game product item instance,
+ /// with additional properties for packaging, quality, and product definition.
///
- ///
- internal ProductInstance(S1Product.ProductItemInstance productInstance) : base(productInstance) { }
+ internal ProductInstance(S1Product.ProductItemInstance productInstance)
+ : base(productInstance)
+ {
+ }
///
- /// Whether this product is currently packaged or not.
+ /// Indicates whether the product instance has applied packaging.
///
- public bool IsPackaged =>
- S1ProductInstance.AppliedPackaging;
+ public bool IsPackaged => S1ProductInstance.AppliedPackaging;
///
- /// The type of packaging applied to this product.
+ /// Provides access to the packaging information applied to the product,
+ /// represented as a specific packaging definition instance.
///
public PackagingDefinition AppliedPackaging =>
new PackagingDefinition(S1ProductInstance.AppliedPackaging);
+
+ ///
+ /// Represents the quality level of the product instance.
+ ///
+ ///
+ /// Quality levels provide a measure of the product's grading, ranging from "Trash" to "Heavenly".
+ ///
+ public Quality Quality => S1ProductInstance.Quality.ToAPI();
+
+ ///
+ /// Gets the definition of the product associated with this instance.
+ ///
+ public ProductDefinition Definition => new ProductDefinition(S1ProductInstance.Definition);
+
+ ///
+ /// Gets the list of properties associated with the product definition.
+ ///
+ ///
+ /// This property provides an unmodifiable list of properties associated
+ /// with the underlying product definition. Each property represents
+ /// a specific characteristic or behavior of the corresponding product.
+ ///
+ public IReadOnlyList Properties => Definition.Properties;
}
}
diff --git a/S1API/Products/Quality.cs b/S1API/Products/Quality.cs
new file mode 100644
index 00000000..d95d2313
--- /dev/null
+++ b/S1API/Products/Quality.cs
@@ -0,0 +1,87 @@
+#if IL2CPPMELON
+using InternalQuality = Il2CppScheduleOne.ItemFramework.EQuality;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using InternalQuality = ScheduleOne.ItemFramework.EQuality;
+#endif
+namespace S1API.Products
+{
+ ///
+ /// Represents the quality levels for items.
+ ///
+ ///
+ /// This enumeration defines various quality tiers that items can belong to. Each tier represents a specific
+ /// standard or grade, ranging from the lowest to the highest.
+ ///
+ public enum Quality
+ {
+ ///
+ /// Represents "Trash" Quality
+ ///
+ Trash = 0,
+
+ ///
+ /// Represents "Poor" Quality
+ ///
+ Poor = 1,
+
+ ///
+ /// Represents "Standard" Quality
+ ///
+ Standard = 2,
+
+ ///
+ /// Represents "Premium" quality
+ ///
+ Premium = 3,
+
+ ///
+ /// Represents "Heavenly" quality
+ ///
+ Heavenly = 4
+ }
+
+ ///
+ /// Provides extension methods for converting between and
+ /// enumerations.
+ ///
+ internal static class QualityExtensions
+ {
+ ///
+ /// Converts an instance of to its corresponding
+ /// representation.
+ ///
+ /// The instance to convert.
+ /// A value that represents the converted quality.
+ internal static Quality ToAPI(this InternalQuality quality)
+ {
+ return quality switch
+ {
+ InternalQuality.Trash => Quality.Trash,
+ InternalQuality.Poor => Quality.Poor,
+ InternalQuality.Standard => Quality.Standard,
+ InternalQuality.Premium => Quality.Premium,
+ InternalQuality.Heavenly => Quality.Heavenly,
+ _ => Quality.Trash,
+ };
+ }
+
+ ///
+ /// Converts an instance of the enum to its corresponding
+ /// enum representation.
+ ///
+ /// The enum value to convert.
+ /// The corresponding enum value.
+ internal static InternalQuality ToInternal(this Quality quality)
+ {
+ return quality switch
+ {
+ Quality.Trash => InternalQuality.Trash,
+ Quality.Poor => InternalQuality.Poor,
+ Quality.Standard => InternalQuality.Standard,
+ Quality.Premium => InternalQuality.Premium,
+ Quality.Heavenly => InternalQuality.Heavenly,
+ _ => InternalQuality.Trash,
+ };
+ }
+ }
+}
diff --git a/S1API/Products/WeedDefinition.cs b/S1API/Products/WeedDefinition.cs
index 8f334711..fb4c0164 100644
--- a/S1API/Products/WeedDefinition.cs
+++ b/S1API/Products/WeedDefinition.cs
@@ -1,40 +1,63 @@
#if (IL2CPPMELON)
using Il2CppScheduleOne.Product;
using S1WeedDefinition = Il2CppScheduleOne.Product.WeedDefinition;
+using S1Properties = Il2CppScheduleOne.Properties;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using ScheduleOne.Product;
using S1WeedDefinition = ScheduleOne.Product.WeedDefinition;
+using S1Properties = ScheduleOne.Properties;
#endif
using S1API.Internal.Utils;
using S1API.Items;
+using System.Collections.Generic;
namespace S1API.Products
{
///
- /// Represents the definition of a Weed product.
+ /// Represents a specific type of weed product definition.
///
public class WeedDefinition : ProductDefinition
{
///
- /// INTERNAL: Strongly typed access to the WeedDefinition.
+ /// INTERNAL: Strongly typed reference to Schedule One's WeedDefinition.
///
internal S1WeedDefinition S1WeedDefinition =>
CrossType.As(S1ItemDefinition);
-
///
- /// Creates a new weed product definition.
+ /// Represents a specific type of weed product definition.
///
- /// The original in-game weed definition.
internal WeedDefinition(S1WeedDefinition definition)
- : base(definition) { }
+ : base(definition)
+ {
+ }
///
- /// Creates an instance of this weed product.
+ /// Creates an instance of the product with the specified quantity.
///
+ /// The quantity of the product to create. Defaults to 1 if not specified.
+ /// An representing the created product instance with the specified quantity.
public override ItemInstance CreateInstance(int quantity = 1) =>
new ProductInstance(CrossType.As(
S1WeedDefinition.GetDefaultInstance(quantity)));
+
+ ///
+ /// Retrieves a list of properties associated with this weed definition.
+ ///
+ /// A list of properties of type S1Properties.Property that are linked with the current weed definition, or an empty list if none are found.
+ public List GetProperties()
+ {
+ var result = new List();
+ var list = S1WeedDefinition?.Properties;
+
+ if (list != null)
+ {
+ for (int i = 0; i < list.Count; i++)
+ result.Add(list[i]);
+ }
+
+ return result;
+ }
}
}
diff --git a/S1API/S1API.csproj b/S1API/S1API.csproj
index 85a23c63..418bcc38 100644
--- a/S1API/S1API.csproj
+++ b/S1API/S1API.csproj
@@ -17,6 +17,7 @@
AnyCPU
S1API
true
+ latest
diff --git a/S1API/Vehicles/LandVehicle.cs b/S1API/Vehicles/LandVehicle.cs
new file mode 100644
index 00000000..3f91bdd7
--- /dev/null
+++ b/S1API/Vehicles/LandVehicle.cs
@@ -0,0 +1,198 @@
+#if (IL2CPPMELON)
+using S1Vehicles = Il2CppScheduleOne.Vehicles;
+using Il2Cpp;
+using Il2CppFishNet;
+using Il2CppFishNet.Connection;
+#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
+using S1Vehicles = ScheduleOne.Vehicles;
+using FishNet;
+using FishNet.Connection;
+#endif
+using System;
+using System.Reflection;
+using UnityEngine;
+using S1API.Logging;
+
+namespace S1API.Vehicles
+{
+ ///
+ /// Represents a land vehicle in the game.
+ ///
+ public class LandVehicle
+ {
+ // Public members intended to be used by modders
+ #region Public Members
+ ///
+ /// Creates a new LandVehicle instance.
+ ///
+ public LandVehicle(string vehicleCode)
+ {
+ var vehiclePrefab = S1Vehicles.VehicleManager.Instance.GetVehiclePrefab(vehicleCode);
+ if (vehiclePrefab == null)
+ {
+ _logger.Error($"SpawnVehicle: '{vehicleCode}' is not a valid vehicle code!");
+ return;
+ }
+
+ var component = UnityEngine.Object.Instantiate(vehiclePrefab.gameObject)
+ .GetComponent();
+
+ component.SetGUID(GUIDManager.GenerateUniqueGUID());
+ S1Vehicles.VehicleManager.Instance.AllVehicles.Add(component);
+
+ S1LandVehicle = component;
+ SetConnection();
+ }
+
+ ///
+ /// Vehicle price.
+ ///
+ public float VehiclePrice
+ {
+ get => S1LandVehicle.VehiclePrice;
+ set => VehiclePriceField?.SetValue(S1LandVehicle, value);
+ }
+
+ ///
+ /// Vehicle's top speed.
+ ///
+ public float TopSpeed
+ {
+ get => S1LandVehicle.TopSpeed;
+ set => S1LandVehicle.TopSpeed = value;
+ }
+
+ ///
+ /// If the vehicle is owned by the player.
+ ///
+ public bool IsPlayerOwned
+ {
+ get => S1LandVehicle.IsPlayerOwned;
+ set => SetIsPlayerOwned(value);
+ }
+
+ ///
+ /// Vehicle's color.
+ ///
+ public VehicleColor Color
+ {
+ get => (VehicleColor)S1LandVehicle.OwnedColor;
+ set => SetColor(value);
+ }
+
+ ///
+ /// Spawns the vehicle in the game world.
+ ///
+ /// Position in the world
+ /// Rotation of the vehicle
+ public void Spawn(Vector3 position, Quaternion rotation)
+ {
+ if (!InstanceFinder.IsServer)
+ {
+ _logger.Warning("Spawn can only be called on the server!");
+ return;
+ }
+
+ if (S1LandVehicle == null)
+ throw new Exception("Unable to spawn vehicle, S1LandVehicle is null!");
+
+ S1LandVehicle.transform.position = position;
+ S1LandVehicle.transform.rotation = rotation;
+ S1Vehicles.VehicleManager.Instance.Spawn(S1LandVehicle.gameObject);
+ }
+
+ #endregion
+
+ // Internal members used by S1API
+ #region Internal Members
+
+ ///
+ /// INTERNAL: The stored reference to the land vehicle in-game (see ).
+ ///
+ internal S1Vehicles.LandVehicle S1LandVehicle = null!;
+
+ ///
+ /// INTERNAL: Creates a LandVehicle instance from an in-game land vehicle instance.
+ ///
+ /// The in-game land vehicle instance.
+ internal LandVehicle(S1Vehicles.LandVehicle landVehicle)
+ {
+ S1LandVehicle = landVehicle;
+ SetConnection();
+ }
+
+ #endregion
+
+ // Private members used by LandVehicle class
+ #region Private Members
+
+ ///
+ /// Logger for the LandVehicle class.
+ ///
+ private static readonly Log _logger = new Log("S1API.LandVehicle");
+
+ ///
+ /// The stored reference to protected vehiclePrice field in the land vehicle in-game.
+ ///
+ private static readonly FieldInfo? VehiclePriceField =
+ typeof(S1Vehicles.LandVehicle).GetField("vehiclePrice", BindingFlags.NonPublic);
+
+ ///
+ /// Connection to the player that owns the vehicle.
+ ///
+ private NetworkConnection? _conn;
+
+ ///
+ /// Sets the connection to the player that owns the vehicle.
+ ///
+ private void SetConnection()
+ {
+ var nm = InstanceFinder.NetworkManager;
+ if (nm.IsClientOnly)
+ {
+ var tempConn = InstanceFinder.ClientManager.Connection;
+ if (tempConn != null && tempConn.IsValid)
+ _conn = tempConn;
+ }
+ else if (nm.IsServerOnly || (nm.IsServer && !nm.IsClient))
+ {
+ var owner = S1LandVehicle.Owner;
+ if (owner != null && owner.IsValid)
+ _conn = owner;
+ }
+ }
+
+ ///
+ /// Helper method to set the vehicle as player owned.
+ ///
+ /// If true, sets vehicle as player owned
+ private void SetIsPlayerOwned(bool isPlayerOwned)
+ {
+ S1LandVehicle.SetIsPlayerOwned(_conn, isPlayerOwned);
+ // make sure to add/remove the vehicle from the player owned vehicles list
+ if (isPlayerOwned)
+ S1Vehicles.VehicleManager.Instance.PlayerOwnedVehicles.Add(S1LandVehicle);
+ else
+ S1Vehicles.VehicleManager.Instance.PlayerOwnedVehicles.Remove(S1LandVehicle);
+ }
+
+ ///
+ /// Helper method to set the vehicle color.
+ ///
+ /// Vehicle's color
+ private void SetColor(VehicleColor color)
+ {
+ var setOwnedColorMethod =
+ typeof(S1Vehicles.LandVehicle).GetMethod("SetOwnedColor",
+ BindingFlags.Instance | BindingFlags.NonPublic);
+ if (setOwnedColorMethod == null)
+ {
+ _logger.Error("SetOwnedColor method not found!");
+ return;
+ }
+
+ setOwnedColorMethod.Invoke(S1LandVehicle, [_conn, (S1Vehicles.Modification.EVehicleColor)color]);
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/S1API/Vehicles/VehicleColor.cs b/S1API/Vehicles/VehicleColor.cs
new file mode 100644
index 00000000..4694d3a5
--- /dev/null
+++ b/S1API/Vehicles/VehicleColor.cs
@@ -0,0 +1,26 @@
+namespace S1API.Vehicles
+{
+ ///
+ /// Represents available colors for vehicles.
+ ///
+ public enum VehicleColor
+ {
+ Black,
+ DarkGrey,
+ LightGrey,
+ White,
+ Yellow,
+ Orange,
+ Red,
+ DullRed,
+ Pink,
+ Purple,
+ Navy,
+ DarkBlue,
+ LightBlue,
+ Cyan,
+ LightGreen,
+ DarkGreen,
+ Custom
+ }
+}
\ No newline at end of file