diff --git a/S1API/Internal/Patches/QuestPatches.cs b/S1API/Internal/Patches/QuestPatches.cs
index dc0dd12a..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.
///
- /// 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")]
+ 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.
+ ///
+ /// 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;
- foreach (Quest quest in QuestManager.Quests)
- quest.SaveInternal(questsPath, ref __result);
+ 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);
+ }
+ }
+
+ }
+ 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,6 +139,12 @@ private static void QuestsLoaderLoad(S1Loaders.QuestsLoader __instance, string m
}
}
+
+ ///
+ /// Executes logic prior to the start of a quest.
+ /// Ensures that linked modded quest data is properly initialized.
+ ///
+ /// 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);
+ }
+
}
///