1- #if ( IL2CPPMELON )
1+ #if ( IL2CPPMELON )
22using S1Loaders = Il2CppScheduleOne . Persistence . Loaders ;
33using S1Datas = Il2CppScheduleOne . Persistence . Datas ;
44using S1Quests = Il2CppScheduleOne . Quests ;
5+ using S1Persistence = Il2CppScheduleOne . Persistence ;
56#elif ( MONOMELON || MONOBEPINEX || IL2CPPBEPINEX )
67using S1Loaders = ScheduleOne . Persistence . Loaders ;
78using S1Datas = ScheduleOne . Persistence . Datas ;
89using S1Quests = ScheduleOne . Quests ;
10+ using S1Persistence = ScheduleOne . Persistence ;
911#endif
10-
1112#if ( IL2CPPMELON || IL2CPPBEPINEX )
1213using Il2CppSystem . Collections . Generic ;
1314#elif ( MONOMELON || MONOBEPINEX )
1920using System . Linq ;
2021using HarmonyLib ;
2122using Newtonsoft . Json ;
22- using S1API . Internal . Abstraction ;
2323using S1API . Internal . Utils ;
2424using S1API . Quests ;
2525using UnityEngine ;
26+ using ISaveable = S1API . Internal . Abstraction . ISaveable ;
2627
2728namespace S1API . Internal . Patches
2829{
2930 /// <summary>
30- /// INTERNAL: All patches related to quests .
31+ /// INTERNAL: Contains patches related to quest processing and custom modifications .
3132 /// </summary>
3233 [ HarmonyPatch ]
3334 internal class QuestPatches
3435 {
3536 /// <summary>
36- /// Patching performed when all quests are saved.
37+ /// Provides a centralized logging mechanism to capture and output messages, warnings,
38+ /// and errors during runtime, using underlying logging frameworks like BepInEx or MelonLoader.
39+ /// </summary>
40+ protected static readonly Logging . Log Logger = new Logging . Log ( "QuestPatches" ) ;
41+
42+ /// <summary>
43+ /// Executes additional logic after quests are saved by the SaveManager.
44+ /// Ensures that directories for modded quests are properly created and that
45+ /// only non-vanilla modded quests are saved into the specified folder.
3746 /// </summary>
38- /// <param name="__instance">Instance of the quest manager.</param>
39- /// <param name="parentFolderPath">Path to the base Quest folder.</param>
40- /// <param name="__result">List of extra saveable data. The game uses this for cleanup later.</param>
41- [ HarmonyPatch ( typeof ( S1Quests . QuestManager ) , "WriteData" ) ]
47+ /// <param name="saveFolderPath">The path to the save folder where quests are being stored.</param>
48+ [ HarmonyPatch ( typeof ( S1Persistence . SaveManager ) , nameof ( S1Persistence . SaveManager . Save ) , typeof ( string ) ) ]
4249 [ HarmonyPostfix ]
43- private static void QuestManagerWriteData ( S1Quests . QuestManager __instance , string parentFolderPath , ref List < string > __result )
50+ private static void SaveManager_Save_Postfix ( string saveFolderPath )
4451 {
45- string questsPath = Path . Combine ( parentFolderPath , "Quests" ) ;
52+ try
53+ {
54+ var saveManager = S1Persistence . SaveManager . Instance ;
55+
56+ string [ ] approved = {
57+ "Modded" ,
58+ Path . Combine ( "Modded" , "Quests" )
59+ } ;
60+
61+ foreach ( var path in approved )
62+ {
63+ if ( ! saveManager . ApprovedBaseLevelPaths . Contains ( path ) )
64+ saveManager . ApprovedBaseLevelPaths . Add ( path ) ;
65+ }
66+
67+ // ✅ Create the directory structure
68+ string questsPath = Path . Combine ( saveFolderPath , "Modded" , "Quests" ) ;
69+ Directory . CreateDirectory ( questsPath ) ;
70+
71+ // ✅ Save only non-vanilla modded quests
72+ foreach ( Quest quest in QuestManager . Quests )
73+ {
74+ if ( ! quest . GetType ( ) . Namespace . StartsWith ( "ScheduleOne" ) )
75+ {
76+ List < string > dummy = new List < string > ( ) ;
77+ quest . SaveInternal ( questsPath , ref dummy ) ;
78+ }
79+ }
4680
47- foreach ( Quest quest in QuestManager . Quests )
48- quest . SaveInternal ( questsPath , ref __result ) ;
81+ }
82+ catch ( Exception ex )
83+ {
84+ Logger . Error ( "[S1API] ❌ Failed to save modded quests:\n " + ex ) ;
85+ }
4986 }
5087
88+
5189 /// <summary>
52- /// Patching performed for when all quests are loaded.
90+ /// Invoked after all base quests are loaded to handle modded quest loading.
91+ /// Loads modded quests from a specific "Modded/Quests" directory and integrates them into the game.
5392 /// </summary>
54- /// <param name="__instance">Instance of the quest loader .</param>
55- /// <param name="mainPath">Path to the base Quest folder .</param>
93+ /// <param name="__instance">The quest loader instance responsible for managing quest load operations .</param>
94+ /// <param name="mainPath">The path to the primary quest directory in the base game .</param>
5695 [ HarmonyPatch ( typeof ( S1Loaders . QuestsLoader ) , "Load" ) ]
5796 [ HarmonyPostfix ]
5897 private static void QuestsLoaderLoad ( S1Loaders . QuestsLoader __instance , string mainPath )
5998 {
60- // Make sure we have a quests directory (fresh saves don't at this point in runtime)
61- if ( ! Directory . Exists ( mainPath ) )
99+ string moddedQuestsPath = Path . Combine (
100+ S1Persistence . LoadManager . Instance . LoadedGameFolderPath ,
101+ "Modded" , "Quests"
102+ ) ;
103+
104+ if ( ! Directory . Exists ( moddedQuestsPath ) )
105+ {
106+ Directory . CreateDirectory ( moddedQuestsPath ) ;
62107 return ;
108+ }
63109
64- string [ ] questDirectories = Directory . GetDirectories ( mainPath )
110+ string [ ] questDirectories = Directory . GetDirectories ( moddedQuestsPath )
65111 . Select ( Path . GetFileName )
66112 . Where ( directory => directory != null && directory . StartsWith ( "Quest_" ) )
67- . ToArray ( ) ! ;
113+ . ToArray ( ) ;
68114
69115 foreach ( string questDirectory in questDirectories )
70116 {
71- string baseQuestPath = Path . Combine ( mainPath , questDirectory ) ;
117+ string baseQuestPath = Path . Combine ( moddedQuestsPath , questDirectory ) ;
72118 __instance . TryLoadFile ( baseQuestPath , out string questDataText ) ;
73119 if ( questDataText == null )
74120 continue ;
75121
76122 S1Datas . QuestData baseQuestData = JsonUtility . FromJson < S1Datas . QuestData > ( questDataText ) ;
77123
78- string questDirectoryPath = Path . Combine ( mainPath , questDirectory ) ;
124+ string questDirectoryPath = Path . Combine ( moddedQuestsPath , questDirectory ) ;
79125 string questDataPath = Path . Combine ( questDirectoryPath , "QuestData" ) ;
80126 if ( ! __instance . TryLoadFile ( questDataPath , out string questText ) )
81127 continue ;
@@ -93,26 +139,12 @@ private static void QuestsLoaderLoad(S1Loaders.QuestsLoader __instance, string m
93139 }
94140 }
95141
142+
96143 /// <summary>
97- /// Patching performed for when stale files are deleted.
144+ /// Executes logic prior to the start of a quest.
145+ /// Ensures that linked modded quest data is properly initialized.
98146 /// </summary>
99- /// <param name="__instance">Instance of the quest manager.</param>
100- /// <param name="parentFolderPath">Path to the base Quest folder.</param>
101- [ HarmonyPatch ( typeof ( S1Quests . QuestManager ) , "DeleteUnapprovedFiles" ) ]
102- [ HarmonyPostfix ]
103- private static void QuestManagerDeleteUnapprovedFiles ( S1Quests . QuestManager __instance , string parentFolderPath )
104- {
105- string questFolder = Path . Combine ( parentFolderPath , "Quests" ) ;
106- string ? [ ] existingQuests = QuestManager . Quests . Select ( quest => quest . SaveFolder ) . ToArray ( ) ;
107-
108- string [ ] unapprovedQuestDirectories = Directory . GetDirectories ( questFolder )
109- . Where ( directory => directory . StartsWith ( "Quest_" ) && ! existingQuests . Contains ( directory ) )
110- . ToArray ( ) ;
111-
112- foreach ( string unapprovedQuestDirectory in unapprovedQuestDirectories )
113- Directory . Delete ( unapprovedQuestDirectory , true ) ;
114- }
115-
147+ /// <param name="__instance">The instance of the quest that is being started.</param>
116148 [ HarmonyPatch ( typeof ( S1Quests . Quest ) , "Start" ) ]
117149 [ HarmonyPrefix ]
118150 private static void QuestStart ( S1Quests . Quest __instance ) =>
0 commit comments