Skip to content

Commit 62bff73

Browse files
authored
Merge pull request #86 from KaBooMa/questpatches
Questpatches
2 parents 0de38fe + a608613 commit 62bff73

2 files changed

Lines changed: 92 additions & 27 deletions

File tree

S1API/Internal/Patches/QuestPatches.cs

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
#if (IL2CPPMELON)
1+
#if (IL2CPPMELON)
22
using S1Loaders = Il2CppScheduleOne.Persistence.Loaders;
33
using S1Datas = Il2CppScheduleOne.Persistence.Datas;
44
using S1Quests = Il2CppScheduleOne.Quests;
5+
using S1Persistence = Il2CppScheduleOne.Persistence;
56
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
67
using S1Loaders = ScheduleOne.Persistence.Loaders;
78
using S1Datas = ScheduleOne.Persistence.Datas;
89
using S1Quests = ScheduleOne.Quests;
10+
using S1Persistence = ScheduleOne.Persistence;
911
#endif
10-
1112
#if (IL2CPPMELON || IL2CPPBEPINEX)
1213
using Il2CppSystem.Collections.Generic;
1314
#elif (MONOMELON || MONOBEPINEX)
@@ -19,63 +20,108 @@
1920
using System.Linq;
2021
using HarmonyLib;
2122
using Newtonsoft.Json;
22-
using S1API.Internal.Abstraction;
2323
using S1API.Internal.Utils;
2424
using S1API.Quests;
2525
using UnityEngine;
26+
using ISaveable = S1API.Internal.Abstraction.ISaveable;
2627

2728
namespace 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.
3739
/// </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")]
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.
46+
/// </summary>
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;
4655

47-
foreach (Quest quest in QuestManager.Quests)
48-
quest.SaveInternal(questsPath, ref __result);
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+
}
80+
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,6 +139,12 @@ private static void QuestsLoaderLoad(S1Loaders.QuestsLoader __instance, string m
93139
}
94140
}
95141

142+
143+
/// <summary>
144+
/// Executes logic prior to the start of a quest.
145+
/// Ensures that linked modded quest data is properly initialized.
146+
/// </summary>
147+
/// <param name="__instance">The instance of the quest that is being started.</param>
96148
[HarmonyPatch(typeof(S1Quests.Quest), "Start")]
97149
[HarmonyPrefix]
98150
private static void QuestStart(S1Quests.Quest __instance) =>

S1API/Items/ItemDefinition.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#if (IL2CPPMELON)
1+
#if (IL2CPPMELON)
22
using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
33
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
44
using S1ItemFramework = ScheduleOne.ItemFramework;
@@ -153,16 +153,29 @@ public override bool Equals(object? obj) =>
153153
/// <param name="a">The first <see cref="ItemDefinition"/> to compare.</param>
154154
/// <param name="b">The second <see cref="ItemDefinition"/> to compare.</param>
155155
/// <returns><c>true</c> if both instances are equal or have the same S1ItemDefinition; otherwise, <c>false</c>.</returns>
156-
public static bool operator ==(ItemDefinition? a, ItemDefinition? b) =>
157-
ReferenceEquals(a, b) || a != null && b != null && a.S1ItemDefinition == b.S1ItemDefinition;
158-
156+
public static bool operator ==(ItemDefinition? a, ItemDefinition? b)
157+
{
158+
if (ReferenceEquals(a, b))
159+
return true;
160+
if (a is null || b is null)
161+
return false;
162+
return ReferenceEquals(a.S1ItemDefinition, b.S1ItemDefinition);
163+
}
159164
/// <summary>
160165
/// Determines whether two <see cref="ItemDefinition"/> instances are not equal.
161166
/// </summary>
162167
/// <param name="a">The first <see cref="ItemDefinition"/> to compare.</param>
163168
/// <param name="b">The second <see cref="ItemDefinition"/> to compare.</param>
164169
/// <returns><c>true</c> if the instances are not equal; otherwise, <c>false</c>.</returns>
165-
public static bool operator !=(ItemDefinition? a, ItemDefinition? b) => !(a == b);
170+
public static bool operator !=(ItemDefinition? a, ItemDefinition? b)
171+
{
172+
if (ReferenceEquals(a, b))
173+
return false;
174+
if (a is null || b is null)
175+
return true;
176+
return !ReferenceEquals(a.S1ItemDefinition, b.S1ItemDefinition);
177+
}
178+
166179
}
167180

168181
/// <summary>

0 commit comments

Comments
 (0)