Skip to content
84 changes: 44 additions & 40 deletions S1API/Internal/Patches/QuestPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
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;
#endif
using S1Persistence = ScheduleOne.Persistence;

#endif
#if (IL2CPPMELON || IL2CPPBEPINEX)
using Il2CppSystem.Collections.Generic;
#elif (MONOMELON || MONOBEPINEX)
Expand All @@ -19,63 +21,80 @@
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
{
/// <summary>
/// INTERNAL: All patches related to quests.
/// INTERNAL: Contains patches specific to quest handling and modification.
/// </summary>
[HarmonyPatch]
internal class QuestPatches
{
protected static readonly Logging.Log Logger = new Logging.Log("QuestPatches");

/// <summary>
/// Patching performed when all quests are saved.
/// Invoked after all quests are saved.
/// Ensures that modded quest data is correctly saved to a designated folder.
/// </summary>
/// <param name="__instance">Instance of the quest manager.</param>
/// <param name="parentFolderPath">Path to the base Quest folder.</param>
/// <param name="__result">List of extra saveable data. The game uses this for cleanup later.</param>
[HarmonyPatch(typeof(S1Quests.QuestManager), "WriteData")]
/// <param name="saveFolderPath">The path to the primary save folder where quest data will be stored.</param>
[HarmonyPatch(typeof(S1Persistence.SaveManager), nameof(S1Persistence.SaveManager.Save), typeof(string))]
[HarmonyPostfix]
private static void QuestManagerWriteData(S1Quests.QuestManager __instance, string parentFolderPath, ref List<string> __result)
private static void SaveManager_Save_Postfix(string saveFolderPath)
{
string questsPath = Path.Combine(parentFolderPath, "Quests");
try
{
string moddedQuestsPath = Path.Combine(saveFolderPath, "..\\Players\\ModdedQuests");

if (!Directory.Exists(moddedQuestsPath))
Directory.CreateDirectory(moddedQuestsPath);

foreach (Quest quest in QuestManager.Quests)
{

foreach (Quest quest in QuestManager.Quests)
quest.SaveInternal(questsPath, ref __result);
List<string> dummy = new List<string>();

quest.SaveInternal(moddedQuestsPath, ref dummy);

}

}
catch (Exception ex)
{
Logger.Error("Failed during SaveManager_Save_Postfix execution.\n" + ex);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider adding an overload in the Log class that takes in the exception and logs it using the built-in overloads that also take in the exceptions.

But it does seem that there's only an overload for the exception signature with Melon. Shame.

throw;
}
}


/// <summary>
/// Patching performed for when all quests are loaded.
/// Patching performed for when all quests are loaded from the modded quests directory.
/// </summary>
/// <param name="__instance">Instance of the quest loader.</param>
/// <param name="mainPath">Path to the base Quest folder.</param>
/// <param name="__instance">Instance of the quest loader responsible for loading quests.</param>
/// <param name="mainPath">Path to the base Quest folder where quests are located.</param>
[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))
return;
string moddedQuestsPath = Path.Combine(mainPath, "..\\Players\\ModdedQuests");

string[] questDirectories = Directory.GetDirectories(mainPath)
string[] questDirectories = Directory.GetDirectories(moddedQuestsPath)
.Select(Path.GetFileName)
.Where(directory => directory != null && directory.StartsWith("Quest_"))
.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<S1Datas.QuestData>(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;
Expand All @@ -93,26 +112,11 @@ private static void QuestsLoaderLoad(S1Loaders.QuestsLoader __instance, string m
}
}


/// <summary>
/// Patching performed for when stale files are deleted.
/// Executes custom initialization logic whenever a quest starts.
/// </summary>
/// <param name="__instance">Instance of the quest manager.</param>
/// <param name="parentFolderPath">Path to the base Quest folder.</param>
[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);
}

/// <param name="__instance">The instance of the quest that is starting.</param>
[HarmonyPatch(typeof(S1Quests.Quest), "Start")]
[HarmonyPrefix]
private static void QuestStart(S1Quests.Quest __instance) =>
Expand Down
23 changes: 18 additions & 5 deletions S1API/Items/ItemDefinition.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#if (IL2CPPMELON)
#if (IL2CPPMELON)
using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1ItemFramework = ScheduleOne.ItemFramework;
Expand Down Expand Up @@ -153,16 +153,29 @@ public override bool Equals(object? obj) =>
/// <param name="a">The first <see cref="ItemDefinition"/> to compare.</param>
/// <param name="b">The second <see cref="ItemDefinition"/> to compare.</param>
/// <returns><c>true</c> if both instances are equal or have the same S1ItemDefinition; otherwise, <c>false</c>.</returns>
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);
}
/// <summary>
/// Determines whether two <see cref="ItemDefinition"/> instances are not equal.
/// </summary>
/// <param name="a">The first <see cref="ItemDefinition"/> to compare.</param>
/// <param name="b">The second <see cref="ItemDefinition"/> to compare.</param>
/// <returns><c>true</c> if the instances are not equal; otherwise, <c>false</c>.</returns>
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);
}

}

/// <summary>
Expand Down
Loading