Skip to content

Commit ebd9c5c

Browse files
authored
Merge pull request #89 from k073l/vehicles
Vehicles
2 parents cdc0257 + 1c91ed9 commit ebd9c5c

13 files changed

Lines changed: 564 additions & 87 deletions

S1API/Entities/NPC.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ public bool ConversationCanBeHidden
592592
/// </summary>
593593
/// <typeparam name="T">The NPC class to get the instance of.</typeparam>
594594
/// <returns></returns>
595-
public static NPC? Get<T>() =>
595+
public static NPC? Get<T>() where T : NPC =>
596596
All.FirstOrDefault(npc => npc.GetType() == typeof(T));
597597

598598
#endregion

S1API/Internal/Patches/QuestPatches.cs

Lines changed: 72 additions & 40 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.
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) =>

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>
Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,61 @@
11
#if (IL2CPPMELON)
22
using Il2CppScheduleOne.Product;
33
using S1CocaineDefinition = Il2CppScheduleOne.Product.CocaineDefinition;
4+
using S1Properties = Il2CppScheduleOne.Properties;
45
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
56
using ScheduleOne.Product;
67
using S1CocaineDefinition = ScheduleOne.Product.CocaineDefinition;
8+
using S1Properties = ScheduleOne.Properties;
79
#endif
810

11+
using System.Collections.Generic;
912
using S1API.Internal.Utils;
1013
using S1API.Items;
1114

1215
namespace S1API.Products
1316
{
1417
/// <summary>
15-
/// Represents the definition of a Cocaine product.
18+
/// Defines the characteristics and behaviors of a cocaine product within the system.
1619
/// </summary>
1720
public class CocaineDefinition : ProductDefinition
1821
{
1922
/// <summary>
20-
/// INTERNAL: Strongly typed access to the CocaineDefinition.
23+
/// Provides internal access to the CocaineDefinition type within the Schedule One system.
2124
/// </summary>
2225
internal S1CocaineDefinition S1CocaineDefinition =>
2326
CrossType.As<S1CocaineDefinition>(S1ItemDefinition);
2427

2528
/// <summary>
26-
/// Creates a new cocaine product definition.
29+
/// Represents the definition of a cocaine product within the system.
2730
/// </summary>
28-
/// <param name="definition">The original in-game cocaine definition.</param>
2931
internal CocaineDefinition(S1CocaineDefinition definition)
30-
: base(definition) { }
32+
: base(definition)
33+
{
34+
}
3135

3236
/// <summary>
33-
/// Creates an instance of this cocaine product.
37+
/// Creates an instance of this product definition with the specified quantity.
3438
/// </summary>
39+
/// <param name="quantity">The quantity of the product to instantiate. Defaults to 1 if not specified.</param>
40+
/// <returns>An <see cref="ItemInstance"/> representing the instantiated product with the specified quantity.</returns>
3541
public override ItemInstance CreateInstance(int quantity = 1) =>
3642
new ProductInstance(CrossType.As<ProductItemInstance>(
3743
S1CocaineDefinition.GetDefaultInstance(quantity)));
44+
45+
/// <summary>
46+
/// Retrieves a list of properties associated with this product definition.
47+
/// </summary>
48+
/// <returns>A list of properties for the product.</returns>
49+
public List<S1Properties.Property> GetProperties()
50+
{
51+
var result = new List<S1Properties.Property>();
52+
var list = S1CocaineDefinition?.Properties;
53+
if (list != null)
54+
{
55+
for (int i = 0; i < list.Count; i++)
56+
result.Add(list[i]);
57+
}
58+
return result;
59+
}
3860
}
3961
}

S1API/Products/MethDefinition.cs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,60 @@
11
#if (IL2CPPMELON)
22
using Il2CppScheduleOne.Product;
33
using S1MethDefinition = Il2CppScheduleOne.Product.MethDefinition;
4+
using S1Properties = Il2CppScheduleOne.Properties;
45
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
56
using ScheduleOne.Product;
67
using S1MethDefinition = ScheduleOne.Product.MethDefinition;
8+
using S1Properties = ScheduleOne.Properties;
79
#endif
810

11+
using System.Collections.Generic;
912
using S1API.Internal.Utils;
1013
using S1API.Items;
11-
1214
namespace S1API.Products
1315
{
1416
/// <summary>
15-
/// Represents the definition of a Meth product.
17+
/// Represents the definition of a meth product within the ScheduleOne product framework.
1618
/// </summary>
1719
public class MethDefinition : ProductDefinition
1820
{
1921
/// <summary>
20-
/// INTERNAL: Strongly typed access to the MethDefinition.
22+
/// INTERNAL: Strongly typed access to S1MethDefinition.
2123
/// </summary>
2224
internal S1MethDefinition S1MethDefinition =>
2325
CrossType.As<S1MethDefinition>(S1ItemDefinition);
2426

2527
/// <summary>
26-
/// Creates a new meth product definition.
28+
/// Represents the definition of a Meth product in the product framework.
2729
/// </summary>
28-
/// <param name="definition">The original in-game meth definition.</param>
2930
internal MethDefinition(S1MethDefinition definition)
30-
: base(definition) { }
31+
: base(definition)
32+
{
33+
}
3134

3235
/// <summary>
33-
/// Creates an instance of this meth product.
36+
/// Creates an instance of this meth product with the specified quantity.
3437
/// </summary>
38+
/// <param name="quantity">The quantity of the product instance to create. Defaults to 1 if not specified.</param>
39+
/// <returns>An instance of <see cref="ItemInstance"/> representing the created meth product.</returns>
3540
public override ItemInstance CreateInstance(int quantity = 1) =>
3641
new ProductInstance(CrossType.As<ProductItemInstance>(
3742
S1MethDefinition.GetDefaultInstance(quantity)));
43+
44+
/// <summary>
45+
/// Retrieves the list of properties associated with the meth product definition.
46+
/// </summary>
47+
/// <returns>A list of properties defined for the meth product.</returns>
48+
public List<S1Properties.Property> GetProperties()
49+
{
50+
var result = new List<S1Properties.Property>();
51+
var list = S1MethDefinition?.Properties;
52+
if (list != null)
53+
{
54+
for (int i = 0; i < list.Count; i++)
55+
result.Add(list[i]);
56+
}
57+
return result;
58+
}
3859
}
3960
}

0 commit comments

Comments
 (0)