diff --git a/.github/workflows/verify-build.yml b/.github/workflows/verify-build.yml
new file mode 100644
index 00000000..8ed2d388
--- /dev/null
+++ b/.github/workflows/verify-build.yml
@@ -0,0 +1,36 @@
+name: Verify Successful Build
+
+on:
+ pull_request:
+ types:
+ - opened
+ - reopened
+ - synchronize
+ branches:
+ - bleeding-edge
+ - stable
+
+jobs:
+ build:
+ name: Verify Successful Build
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Repo
+ uses: actions/checkout@v4
+
+ - name: Clone Game Assemblies
+ run: |
+ git clone https://x-access-token:${{ secrets.GH_PAT }}@github.com/KaBooMa/ScheduleOneAssemblies.git ./ScheduleOneAssemblies
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+
+ - name: Restore .NET Dependencies
+ run: dotnet restore
+
+ - name: Run .NET Build for Mono
+ run: dotnet build ./S1API/S1API.csproj -c Mono -f netstandard2.1
+
+ - name: Run .NET Build for Il2Cpp
+ run: dotnet build ./S1API/S1API.csproj -c Il2Cpp -f netstandard2.1
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 67dd7af1..4ed0e2fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,6 @@
obj/
bin/
*.user
-*Assemblies/
\ No newline at end of file
+
+# Local assembly references
+ScheduleOneAssemblies/
\ No newline at end of file
diff --git a/S1API/DeadDrops/DeadDropInstance.cs b/S1API/DeadDrops/DeadDropInstance.cs
index d1f9e4b9..ee49c249 100644
--- a/S1API/DeadDrops/DeadDropInstance.cs
+++ b/S1API/DeadDrops/DeadDropInstance.cs
@@ -4,6 +4,7 @@
using S1Economy = ScheduleOne.Economy;
#endif
+using System.Linq;
using S1API.Internal.Abstraction;
using S1API.Storages;
using UnityEngine;
@@ -13,7 +14,7 @@ namespace S1API.DeadDrops
///
/// Represents a dead drop in the scene.
///
- public class DeadDropInstance : ISaveable
+ public class DeadDropInstance : IGUIDReference
{
///
/// INTERNAL: Stores a reference to the game dead drop instance.
@@ -32,16 +33,24 @@ public class DeadDropInstance : ISaveable
internal DeadDropInstance(S1Economy.DeadDrop deadDrop) =>
S1DeadDrop = deadDrop;
+ ///
+ /// INTERNAL: Gets a dead drop from a GUID value.
+ ///
+ ///
+ ///
+ internal static DeadDropInstance? GetFromGUID(string guid) =>
+ DeadDropManager.All.FirstOrDefault(deadDrop => deadDrop.GUID == guid);
+
///
/// The unique identifier assigned for this dead drop.
///
public string GUID =>
S1DeadDrop.GUID.ToString();
-
+
///
/// The storage container associated with this dead drop.
///
- public StorageInstance StorageInstance =>
+ public StorageInstance Storage =>
_cachedStorage ??= new StorageInstance(S1DeadDrop.Storage);
///
diff --git a/S1API/Internal/Abstraction/GUIDReferenceConverter.cs b/S1API/Internal/Abstraction/GUIDReferenceConverter.cs
new file mode 100644
index 00000000..88c8796c
--- /dev/null
+++ b/S1API/Internal/Abstraction/GUIDReferenceConverter.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Reflection;
+using Newtonsoft.Json;
+using S1API.Internal.Utils;
+
+namespace S1API.Internal.Abstraction
+{
+ ///
+ /// INTERNAL: JSON Converter to handle GUID referencing classes when saved and loaded.
+ ///
+ internal class GUIDReferenceConverter : JsonConverter
+ {
+ ///
+ /// TODO
+ ///
+ ///
+ ///
+ public override bool CanConvert(Type objectType) =>
+ typeof(IGUIDReference).IsAssignableFrom(objectType);
+
+ ///
+ /// TODO
+ ///
+ ///
+ ///
+ ///
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+ {
+ if (value is IGUIDReference reference)
+ {
+ writer.WriteValue(reference.GUID);
+ }
+ else
+ {
+ writer.WriteNull();
+ }
+ }
+
+ ///
+ /// TODO
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+ {
+ string? guid = reader.Value?.ToString();
+ if (string.IsNullOrEmpty(guid))
+ return null;
+
+ MethodInfo? getGUIDMethod = ReflectionUtils.GetMethod(objectType, "GetFromGUID", BindingFlags.NonPublic | BindingFlags.Static);
+ if (getGUIDMethod == null)
+ throw new Exception($"The type {objectType.Name} does not have a valid implementation of the GetFromGUID(string guid) method!");
+
+ return getGUIDMethod.Invoke(null, new object[] { guid });
+ }
+
+ ///
+ /// TODO
+ ///
+ public override bool CanRead => true;
+ }
+}
\ No newline at end of file
diff --git a/S1API/Internal/Abstraction/IGUIDReference.cs b/S1API/Internal/Abstraction/IGUIDReference.cs
new file mode 100644
index 00000000..15579032
--- /dev/null
+++ b/S1API/Internal/Abstraction/IGUIDReference.cs
@@ -0,0 +1,14 @@
+namespace S1API.Internal.Abstraction
+{
+ ///
+ /// INTERNAL: Represents a class that should serialize by GUID instead of values directly.
+ /// This is important to utilize on instances such as dead drops, item definitions, etc.
+ ///
+ internal interface IGUIDReference
+ {
+ ///
+ /// The GUID associated with the object.
+ ///
+ public string GUID { get; }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Internal/Abstraction/IRegisterable.cs b/S1API/Internal/Abstraction/IRegisterable.cs
new file mode 100644
index 00000000..7ac0b898
--- /dev/null
+++ b/S1API/Internal/Abstraction/IRegisterable.cs
@@ -0,0 +1,28 @@
+namespace S1API.Internal.Abstraction
+{
+ ///
+ /// INTERNAL: Provides rigidity for registerable instance wrappers.
+ ///
+ internal interface IRegisterable
+ {
+ ///
+ /// INTERNAL: Called upon creation of the instance.
+ ///
+ void CreateInternal();
+
+ ///
+ /// INTERNAL: Called upon destruction of the instance.
+ ///
+ void DestroyInternal();
+
+ ///
+ /// Called upon creation of the instance.
+ ///
+ void OnCreated();
+
+ ///
+ /// Called upon destruction of the instance.
+ ///
+ void OnDestroyed();
+ }
+}
\ No newline at end of file
diff --git a/S1API/Internal/Abstraction/ISaveable.cs b/S1API/Internal/Abstraction/ISaveable.cs
index e274edc2..6ac86151 100644
--- a/S1API/Internal/Abstraction/ISaveable.cs
+++ b/S1API/Internal/Abstraction/ISaveable.cs
@@ -1,14 +1,49 @@
-namespace S1API.Internal.Abstraction
+#if (MONO)
+using System.Collections.Generic;
+#elif (IL2CPP)
+using Il2CppSystem.Collections.Generic;
+#endif
+
+using Newtonsoft.Json;
+
+namespace S1API.Internal.Abstraction
{
///
- /// INTERNAL: Represents a class that should serialize by GUID instead of values directly.
- /// This is important to utilize on instanced objects such as dead drops.
+ /// INTERNAL: Provides rigidity for saveable instance wrappers.
///
- internal interface ISaveable
+ internal interface ISaveable : IRegisterable
{
///
- /// The GUID assocated with the object.
+ /// INTERNAL: Called when saving the instance.
///
- public string GUID { get; }
+ /// Path to save to.
+ /// Manipulation of the base game saveable lists.
+ void SaveInternal(string path, ref List extraSaveables);
+
+ ///
+ /// INTERNAL: Called when loading the instance.
+ ///
+ ///
+ void LoadInternal(string folderPath);
+
+ ///
+ /// Called when saving the instance.
+ ///
+ void OnSaved();
+
+ ///
+ /// Called when loading the instance.
+ ///
+ void OnLoaded();
+
+ ///
+ /// INTERNAL: Standard serialization settings to apply for all saveables.
+ ///
+ internal static JsonSerializerSettings SerializerSettings =>
+ new JsonSerializerSettings
+ {
+ ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+ Converters = new System.Collections.Generic.List() { new GUIDReferenceConverter() }
+ };
}
}
\ No newline at end of file
diff --git a/S1API/Internal/Abstraction/Registerable.cs b/S1API/Internal/Abstraction/Registerable.cs
new file mode 100644
index 00000000..60009fb5
--- /dev/null
+++ b/S1API/Internal/Abstraction/Registerable.cs
@@ -0,0 +1,55 @@
+namespace S1API.Internal.Abstraction
+{
+ ///
+ /// INTERNAL: A registerable base class for use internally.
+ /// Not intended for modder use.
+ ///
+ public abstract class Registerable : IRegisterable
+ {
+ ///
+ /// TODO
+ ///
+ void IRegisterable.CreateInternal() =>
+ CreateInternal();
+
+ ///
+ /// TODO
+ ///
+ internal virtual void CreateInternal() =>
+ OnCreated();
+
+ ///
+ /// TODO
+ ///
+ void IRegisterable.DestroyInternal() =>
+ DestroyInternal();
+
+ ///
+ /// TODO
+ ///
+ internal virtual void DestroyInternal() =>
+ OnDestroyed();
+
+ ///
+ /// TODO
+ ///
+ void IRegisterable.OnCreated() =>
+ OnCreated();
+
+ ///
+ /// TODO
+ ///
+ protected virtual void OnCreated() { }
+
+ ///
+ /// TODO
+ ///
+ void IRegisterable.OnDestroyed() =>
+ OnDestroyed();
+
+ ///
+ /// TODO
+ ///
+ protected virtual void OnDestroyed() { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Saveables/Saveable.cs b/S1API/Internal/Abstraction/Saveable.cs
similarity index 62%
rename from S1API/Saveables/Saveable.cs
rename to S1API/Internal/Abstraction/Saveable.cs
index 33e32e1d..b40c61aa 100644
--- a/S1API/Saveables/Saveable.cs
+++ b/S1API/Internal/Abstraction/Saveable.cs
@@ -1,23 +1,33 @@
-using System;
+#if (MONO)
using System.Collections.Generic;
+#elif (IL2CPP)
+using Il2CppSystem.Collections.Generic;
+#endif
+
+using System;
using System.IO;
using System.Reflection;
-using MelonLoader;
using Newtonsoft.Json;
-using UnityEngine;
+using S1API.Internal.Utils;
+using S1API.Saveables;
-namespace S1API.Saveables
+namespace S1API.Internal.Abstraction
{
///
- /// Generic wrapper for saveable classes.
+ /// INTERNAL: Generic wrapper for saveable classes.
/// Intended for use within the API only.
///
- public abstract class Saveable
+ public abstract class Saveable : Registerable, ISaveable
{
- internal virtual void InitializeInternal(GameObject gameObject, string guid = "") { }
-
- internal virtual void StartInternal() => OnStarted();
+ ///
+ /// TODO
+ ///
+ void ISaveable.LoadInternal(string folderPath) =>
+ LoadInternal(folderPath);
+ ///
+ /// TODO
+ ///
internal virtual void LoadInternal(string folderPath)
{
FieldInfo[] saveableFields = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
@@ -27,7 +37,6 @@ internal virtual void LoadInternal(string folderPath)
if (saveableFieldAttribute == null)
continue;
- MelonLogger.Msg($"Loading field {saveableField.Name}");
string filename = saveableFieldAttribute.SaveName.EndsWith(".json")
? saveableFieldAttribute.SaveName
: $"{saveableFieldAttribute.SaveName}.json";
@@ -36,19 +45,27 @@ internal virtual void LoadInternal(string folderPath)
if (!File.Exists(saveDataPath))
continue;
- MelonLogger.Msg($"reading json for field {saveableField.Name}");
string json = File.ReadAllText(saveDataPath);
Type type = saveableField.FieldType;
- object? value = JsonConvert.DeserializeObject(json, type);
+ object? value = JsonConvert.DeserializeObject(json, type, ISaveable.SerializerSettings);
saveableField.SetValue(this, value);
}
OnLoaded();
}
-
- internal virtual void SaveInternal(string path, ref List extraSaveables)
+
+ ///
+ /// TODO
+ ///
+ void ISaveable.SaveInternal(string folderPath, ref List extraSaveables) =>
+ SaveInternal(folderPath, ref extraSaveables);
+
+ ///
+ /// TODO
+ ///
+ internal virtual void SaveInternal(string folderPath, ref List extraSaveables)
{
- FieldInfo[] saveableFields = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ FieldInfo[] saveableFields = ReflectionUtils.GetAllFields(GetType(), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (FieldInfo saveableField in saveableFields)
{
SaveableField saveableFieldAttribute = saveableField.GetCustomAttribute();
@@ -59,7 +76,7 @@ internal virtual void SaveInternal(string path, ref List extraSaveables)
? saveableFieldAttribute.SaveName
: $"{saveableFieldAttribute.SaveName}.json";
- string saveDataPath = Path.Combine(path, saveFileName);
+ string saveDataPath = Path.Combine(folderPath, saveFileName);
object value = saveableField.GetValue(this);
if (value == null)
@@ -72,7 +89,7 @@ internal virtual void SaveInternal(string path, ref List extraSaveables)
extraSaveables.Add(saveFileName);
// Write our data
- string data = JsonConvert.SerializeObject(value, Formatting.Indented);
+ string data = JsonConvert.SerializeObject(value, Formatting.Indented, ISaveable.SerializerSettings);
File.WriteAllText(saveDataPath, data);
}
}
@@ -80,8 +97,26 @@ internal virtual void SaveInternal(string path, ref List extraSaveables)
OnSaved();
}
- protected virtual void OnStarted() { }
+ ///
+ /// TODO
+ ///
+ void ISaveable.OnLoaded() =>
+ OnLoaded();
+
+ ///
+ /// TODO
+ ///
protected virtual void OnLoaded() { }
+
+ ///
+ /// TODO
+ ///
+ void ISaveable.OnSaved() =>
+ OnSaved();
+
+ ///
+ /// TODO
+ ///
protected virtual void OnSaved() { }
}
}
\ No newline at end of file
diff --git a/S1API/Internal/Patches/NPCPatches.cs b/S1API/Internal/Patches/NPCPatches.cs
index 2bdc0cd3..c4304982 100644
--- a/S1API/Internal/Patches/NPCPatches.cs
+++ b/S1API/Internal/Patches/NPCPatches.cs
@@ -14,7 +14,6 @@
using HarmonyLib;
using S1API.Internal.Utils;
using S1API.NPCs;
-using UnityEngine;
namespace S1API.Internal.Patches
{
@@ -29,7 +28,7 @@ internal class NPCPatches
///
/// List of all custom NPCs currently created.
///
- private static System.Collections.Generic.List _npcs = new System.Collections.Generic.List();
+ private static readonly System.Collections.Generic.List NPCs = new System.Collections.Generic.List();
///
/// Patching performed for when game NPCs are loaded.
@@ -42,10 +41,8 @@ private static void NPCsLoadersLoad(S1Loaders.NPCsLoader __instance, string main
{
foreach (Type type in ReflectionUtils.GetDerivedClasses())
{
- GameObject gameObject = new GameObject(type.Name);
NPC customNPC = (NPC)Activator.CreateInstance(type);
- customNPC.InitializeInternal(gameObject);
- _npcs.Add(customNPC);
+ NPCs.Add(customNPC);
string npcPath = Path.Combine(mainPath, customNPC.S1NPC.SaveFolderName);
customNPC.LoadInternal(npcPath);
}
@@ -58,21 +55,18 @@ private static void NPCsLoadersLoad(S1Loaders.NPCsLoader __instance, string main
[HarmonyPatch(typeof(S1NPCs.NPC), "Start")]
[HarmonyPostfix]
private static void NPCStart(S1NPCs.NPC __instance) =>
- _npcs.FirstOrDefault(npc => npc.S1NPC == __instance)?.StartInternal();
+ NPCs.FirstOrDefault(npc => npc.S1NPC == __instance)?.CreateInternal();
///
/// Patching performed for when an NPC calls to save data.
///
/// Instance of the NPC
- /// Path to this NPCs folder.
+ /// Path to the base NPC folder.
///
[HarmonyPatch(typeof(S1NPCs.NPC), "WriteData")]
[HarmonyPostfix]
- private static void NPCWriteData(S1NPCs.NPC __instance, string parentFolderPath, ref List __result)
- {
- System.Collections.Generic.List list = __result.ToArray().ToList();
- _npcs.FirstOrDefault(npc => npc.S1NPC == __instance)?.SaveInternal(parentFolderPath, ref list);
- }
+ private static void NPCWriteData(S1NPCs.NPC __instance, string parentFolderPath, ref List __result) =>
+ NPCs.FirstOrDefault(npc => npc.S1NPC == __instance)?.SaveInternal(parentFolderPath, ref __result);
///
/// Patching performed for when an NPC is destroyed.
@@ -82,12 +76,12 @@ private static void NPCWriteData(S1NPCs.NPC __instance, string parentFolderPath,
[HarmonyPostfix]
private static void NPCOnDestroy(S1NPCs.NPC __instance)
{
- _npcs.RemoveAll(npc => npc.S1NPC == __instance);
- NPC? npc = _npcs.FirstOrDefault(npc => npc.S1NPC == __instance);
+ NPCs.RemoveAll(npc => npc.S1NPC == __instance);
+ NPC? npc = NPCs.FirstOrDefault(npc => npc.S1NPC == __instance);
if (npc == null)
return;
- _npcs.Remove(npc);
+ NPCs.Remove(npc);
}
}
}
\ No newline at end of file
diff --git a/S1API/Internal/Patches/QuestPatches.cs b/S1API/Internal/Patches/QuestPatches.cs
index dc849e9a..1865b935 100644
--- a/S1API/Internal/Patches/QuestPatches.cs
+++ b/S1API/Internal/Patches/QuestPatches.cs
@@ -15,8 +15,10 @@
using System.Linq;
using HarmonyLib;
using Newtonsoft.Json;
+using S1API.Internal.Abstraction;
using S1API.Internal.Utils;
using S1API.Quests;
+using UnityEngine;
namespace S1API.Internal.Patches
{
@@ -36,18 +38,10 @@ internal class QuestPatches
[HarmonyPostfix]
private static void QuestManagerWriteData(S1Quests.QuestManager __instance, string parentFolderPath, ref List __result)
{
- System.Collections.Generic.List list = __result.ToArray().ToList();
-
string questsPath = Path.Combine(parentFolderPath, "Quests");
foreach (Quest quest in QuestManager.Quests)
- {
- string questDataPath = Path.Combine(questsPath, quest.S1Quest.SaveFolderName);
- if (!Directory.Exists(questDataPath))
- Directory.CreateDirectory(questDataPath);
-
- quest.SaveInternal(questDataPath, ref list);
- }
+ quest.SaveInternal(questsPath, ref __result);
}
///
@@ -56,7 +50,7 @@ private static void QuestManagerWriteData(S1Quests.QuestManager __instance, stri
/// Instance of the quest loader.
/// Path to the base Quest folder.
[HarmonyPatch(typeof(S1Loaders.QuestsLoader), "Load")]
- [HarmonyPrefix]
+ [HarmonyPostfix]
private static void QuestsLoaderLoad(S1Loaders.QuestsLoader __instance, string mainPath)
{
string[] questDirectories = Directory.GetDirectories(mainPath)
@@ -71,18 +65,18 @@ private static void QuestsLoaderLoad(S1Loaders.QuestsLoader __instance, string m
if (questDataText == null)
continue;
- S1Datas.QuestData? baseQuestData = JsonConvert.DeserializeObject(questDataText);
+ S1Datas.QuestData baseQuestData = JsonUtility.FromJson(questDataText);
string questDirectoryPath = Path.Combine(mainPath, questDirectory);
string questDataPath = Path.Combine(questDirectoryPath, "QuestData");
if (!__instance.TryLoadFile(questDataPath, out string questText))
continue;
- QuestData? questData = JsonConvert.DeserializeObject(questText);
- if (questData?.QuestType == null)
+ QuestData? questData = JsonConvert.DeserializeObject(questText, ISaveable.SerializerSettings);
+ if (questData?.ClassName == null)
continue;
- Type? questType = ReflectionUtils.GetTypeByName(questData.QuestType);
+ Type? questType = ReflectionUtils.GetTypeByName(questData.ClassName);
if (questType == null || !typeof(Quest).IsAssignableFrom(questType))
continue;
@@ -114,7 +108,7 @@ private static void QuestManagerDeleteUnapprovedFiles(S1Quests.QuestManager __in
[HarmonyPatch(typeof(S1Quests.Quest), "Start")]
[HarmonyPrefix]
private static void QuestStart(S1Quests.Quest __instance) =>
- QuestManager.Quests.FirstOrDefault(quest => quest.S1Quest == __instance)?.StartInternal();
+ QuestManager.Quests.FirstOrDefault(quest => quest.S1Quest == __instance)?.CreateInternal();
/////// TODO: Quests doesn't have OnDestroy. Find another way to clean up
// [HarmonyPatch(typeof(S1Quests.Quest), "OnDestroy")]
diff --git a/S1API/Internal/Utils/CrossType.cs b/S1API/Internal/Utils/CrossType.cs
index 1cab12ef..5ce09684 100644
--- a/S1API/Internal/Utils/CrossType.cs
+++ b/S1API/Internal/Utils/CrossType.cs
@@ -1,12 +1,9 @@
#if (MONO)
using System;
-using UnityEngine;
# elif (IL2CPP)
-
using Il2CppSystem;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
-using MelonLoader;
#endif
namespace S1API.Internal.Utils
diff --git a/S1API/Internal/Utils/ReflectionUtils.cs b/S1API/Internal/Utils/ReflectionUtils.cs
index cfb6c603..ff22e152 100644
--- a/S1API/Internal/Utils/ReflectionUtils.cs
+++ b/S1API/Internal/Utils/ReflectionUtils.cs
@@ -3,13 +3,6 @@
using System.Linq;
using System.Reflection;
-#if (IL2CPP)
-// using Il2CppInterop.Runtime;
-// using Il2CppSystem;
-// using Il2CppSystem.Collections.Generic;
-#elif (MONO)
-#endif
-
namespace S1API.Internal.Utils
{
///
@@ -43,11 +36,11 @@ internal static List GetDerivedClasses()
}
///
- /// Gets all types by their name.
+ /// INTERNAL: Gets all types by their name.
///
/// The name of the type.
/// The actual type identified by the name.
- public static Type? GetTypeByName(string typeName)
+ internal static Type? GetTypeByName(string typeName)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
@@ -61,5 +54,43 @@ internal static List GetDerivedClasses()
return null;
}
+
+ ///
+ /// INTERNAL: Recursively gets fields from a class down to the object type.
+ ///
+ /// The type you want to recursively search.
+ /// The binding flags to apply during the search.
+ ///
+ internal static FieldInfo[] GetAllFields(Type? type, BindingFlags bindingFlags)
+ {
+ List fieldInfos = new List();
+ while (type != null && type != typeof(object))
+ {
+ fieldInfos.AddRange(type.GetFields(bindingFlags));
+ type = type.BaseType;
+ }
+ return fieldInfos.ToArray();
+ }
+
+ ///
+ /// INTERNAL: Recursively searches for a method by name from a class down to the object type.
+ ///
+ /// The type you want to recursively search.
+ /// The name of the method you're searching for.
+ /// The binding flags to apply during the search.
+ ///
+ public static MethodInfo? GetMethod(Type? type, string methodName, BindingFlags bindingFlags)
+ {
+ while (type != null && type != typeof(object))
+ {
+ MethodInfo? method = type.GetMethod(methodName, bindingFlags);
+ if (method != null)
+ return method;
+
+ type = type.BaseType;
+ }
+
+ return null;
+ }
}
}
\ No newline at end of file
diff --git a/S1API/Items/ItemDefinition.cs b/S1API/Items/ItemDefinition.cs
index 943c69f5..b7884c67 100644
--- a/S1API/Items/ItemDefinition.cs
+++ b/S1API/Items/ItemDefinition.cs
@@ -4,7 +4,6 @@
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
-using MelonLoader;
using S1API.Internal.Abstraction;
namespace S1API.Items
@@ -14,7 +13,7 @@ namespace S1API.Items
/// NOTE: A definition is "what" the item is. For example, "This is a `Soda`".
/// Any instanced items in the game will be a instead.
///
- public class ItemDefinition : ISaveable
+ public class ItemDefinition : IGUIDReference
{
///
/// INTERNAL: A reference to the item definition in the game.
@@ -27,6 +26,14 @@ public class ItemDefinition : ISaveable
///
internal ItemDefinition(S1ItemFramework.ItemDefinition s1ItemDefinition) =>
S1ItemDefinition = s1ItemDefinition;
+
+ ///
+ /// INTERNAL: Gets an item definition from a GUID.
+ ///
+ /// The GUID to look for
+ /// The applicable item definition, if found.
+ internal static ItemDefinition GetFromGUID(string guid) =>
+ ItemManager.GetItemDefinition(guid);
///
/// Performs an equals check on the game item definition instance.
@@ -51,7 +58,6 @@ public override int GetHashCode() =>
/// Whether the item definitions are the same or not.
public static bool operator ==(ItemDefinition? left, ItemDefinition? right)
{
- MelonLogger.Msg($"Doing a == comparison");
if (ReferenceEquals(left, right)) return true;
return left?.S1ItemDefinition == right?.S1ItemDefinition;
}
@@ -70,7 +76,7 @@ public override int GetHashCode() =>
///
public virtual string GUID =>
S1ItemDefinition.ID;
-
+
///
/// The unique identifier assigned to this item definition.
///
diff --git a/S1API/Items/ItemSlotInstance.cs b/S1API/Items/ItemSlotInstance.cs
index 7afbd21a..5b6d750b 100644
--- a/S1API/Items/ItemSlotInstance.cs
+++ b/S1API/Items/ItemSlotInstance.cs
@@ -6,7 +6,6 @@
using S1Product = ScheduleOne.Product;
#endif
-using MelonLoader;
using S1API.Internal.Utils;
using S1API.Money;
using S1API.Products;
diff --git a/S1API/NPCs/NPC.cs b/S1API/NPCs/NPC.cs
index 4aab1daf..d53dc27d 100644
--- a/S1API/NPCs/NPC.cs
+++ b/S1API/NPCs/NPC.cs
@@ -12,8 +12,6 @@
using S1Vehicles = Il2CppScheduleOne.Vehicles;
using S1Vision = Il2CppScheduleOne.Vision;
using S1NPCs = Il2CppScheduleOne.NPCs;
-using S1Persistence = Il2CppScheduleOne.Persistence;
-using S1Datas = Il2CppScheduleOne.Persistence.Datas;
using Il2CppSystem.Collections.Generic;
#elif (MONO)
using S1DevUtilities = ScheduleOne.DevUtilities;
@@ -35,8 +33,8 @@
#endif
using System;
-using MelonLoader;
-using S1API.Saveables;
+using System.IO;
+using S1API.Internal.Abstraction;
using UnityEngine;
using UnityEngine.Events;
@@ -47,48 +45,39 @@ namespace S1API.NPCs
///
public abstract class NPC : Saveable
{
- ///
- /// The first name to assign to the NPC.
- ///
- protected abstract string FirstName { get; }
-
- ///
- /// The last name to assign to the NPC.
- ///
- protected abstract string LastName { get; }
-
- ///
- /// The unique identifier to give the NPC.
- ///
- protected abstract string ID { get; }
-
///
/// A list of text responses you've added to your NPC.
///
protected readonly System.Collections.Generic.List Responses =
new System.Collections.Generic.List();
- internal S1NPCs.NPC S1NPC => _s1NPC ?? throw new InvalidOperationException("S1NPC not initialized");
- private S1NPCs.NPC? _s1NPC;
+ internal readonly S1NPCs.NPC S1NPC;
- private GameObject? _gameObject;
+ private readonly GameObject _gameObject;
- internal override void InitializeInternal(GameObject gameObject, string guid = "")
+ ///
+ /// Base constructor for a new NPC.
+ /// Intended to be wrapped in your derived class constructor such as:
+ /// public class MyNPC : NPC ...
+ /// public MyNPC() : base(id, fname, lname) { ... }
+ ///
+ /// The unique identifier for this NPC
+ ///
+ ///
+ public NPC(string guid, string firstName, string lastName)
{
- MelonLogger.Msg("Our NPC is awake!");
- _gameObject = gameObject;
+ _gameObject = new GameObject("NPC");
// Deactivate game object til we're done
_gameObject.SetActive(false);
// Setup the base NPC class
- _s1NPC = _gameObject.AddComponent();
- S1NPC.FirstName = FirstName;
- S1NPC.LastName = LastName;
- S1NPC.ID = ID;
+ S1NPC = _gameObject.AddComponent();
+ S1NPC.FirstName = firstName;
+ S1NPC.LastName = lastName;
+ S1NPC.ID = guid;
S1NPC.BakedGUID = Guid.NewGuid().ToString();
S1NPC.MugshotSprite = S1DevUtilities.PlayerSingleton.Instance.AppIcon;
- MelonLogger.Msg("Added S1NPC");
// ReSharper disable once UseObjectOrCollectionInitializer
S1NPC.ConversationCategories = new List();
@@ -101,14 +90,13 @@ internal override void InitializeInternal(GameObject gameObject, string guid = "
MethodInfo createConvoMethod = AccessTools.Method(typeof(S1NPCs.NPC), "CreateMessageConversation");
createConvoMethod.Invoke(S1NPC, null);
#endif
- MelonLogger.Msg("Setup Convo");
// Add UnityEvents for NPCHealth
S1NPC.Health = _gameObject.GetComponent();
S1NPC.Health.onDie = new UnityEvent();
S1NPC.Health.onKnockedOut = new UnityEvent();
S1NPC.Health.Invincible = true;
- MelonLogger.Msg("Added Health");
+ S1NPC.Health.MaxHealth = 100f;
// Awareness behaviour
GameObject awarenessObject = new GameObject("NPCAwareness");
@@ -124,14 +112,12 @@ internal override void InitializeInternal(GameObject gameObject, string guid = "
S1NPC.awareness.onNoticedPlayerViolatingCurfew = new UnityEvent();
S1NPC.awareness.onNoticedSuspiciousPlayer = new UnityEvent();
S1NPC.awareness.Listener = _gameObject.AddComponent();
- MelonLogger.Msg("Added Awareness");
// Response to actions like gunshots, drug deals, etc.
GameObject responsesObject = new GameObject("NPCResponses");
responsesObject.SetActive(false);
responsesObject.transform.SetParent(_gameObject.transform);
S1NPC.awareness.Responses = responsesObject.AddComponent();
- MelonLogger.Msg("Added behaviour responses");
// Vision cone object and behaviour
GameObject visionObject = new GameObject("VisionCone");
@@ -142,7 +128,6 @@ internal override void InitializeInternal(GameObject gameObject, string guid = "
// Suspicious ? icon in world space
S1NPC.awareness.VisionCone.QuestionMarkPopup = _gameObject.AddComponent();
- MelonLogger.Msg("Added vision cone");
// Interaction behaviour
#if (IL2CPP)
@@ -151,7 +136,6 @@ internal override void InitializeInternal(GameObject gameObject, string guid = "
FieldInfo intObjField = AccessTools.Field(typeof(S1NPCs.NPC), "intObj");
intObjField.SetValue(S1NPC, _gameObject.AddComponent());
#endif
- MelonLogger.Msg("Added Interaction");
// Relationship data
S1NPC.RelationData = new S1Relation.NPCRelationData();
@@ -165,11 +149,9 @@ void OnUnlockAction(S1Relation.NPCRelationData.EUnlockType unlockType, bool noti
}
S1NPC.RelationData.onUnlocked += (Action)OnUnlockAction;
- MelonLogger.Msg("Added relation data");
// Inventory behaviour
S1NPCs.NPCInventory inventory = _gameObject.AddComponent();
- MelonLogger.Msg("Added inventory");
// Pickpocket behaviour
inventory.PickpocketIntObj = _gameObject.AddComponent();
@@ -179,29 +161,24 @@ void OnUnlockAction(S1Relation.NPCRelationData.EUnlockType unlockType, bool noti
// Register NPC in registry
S1NPCs.NPCManager.NPCRegistry.Add(S1NPC);
- MelonLogger.Msg("registered");
- // MelonLogger.Msg("Spawning network object...");
// NetworkObject networkObject = gameObject.AddComponent();
// // networkObject.NetworkBehaviours = InstanceFinder.NetworkManager;
// PropertyInfo networkBehavioursProperty = AccessTools.Property(typeof(NetworkObject), "NetworkBehaviours");
// networkBehavioursProperty.SetValue(networkObject, new [] { this });
- // MelonLogger.Msg("Custom NPC is awake!");
// Enable our custom gameObjects so they can initialize
- MelonLogger.Msg("setting active...");
_gameObject.SetActive(true);
visionObject.SetActive(true);
responsesObject.SetActive(true);
awarenessObject.SetActive(true);
-
- MelonLogger.Msg("NPC added successfully.");
-
- base.InitializeInternal(gameObject, guid);
}
- internal override void StartInternal()
+ ///
+ /// INTERNAL: Initializes the responses that have been added / loaded
+ ///
+ internal override void CreateInternal()
{
// Assign responses to our tracked responses
foreach (S1Messaging.Response s1Response in S1NPC.MSGConversation.currentResponses)
@@ -210,8 +187,14 @@ internal override void StartInternal()
Responses.Add(response);
OnResponseLoaded(response);
}
+
+ base.CreateInternal();
+ }
- base.StartInternal();
+ internal override void SaveInternal(string folderPath, ref List extraSaveables)
+ {
+ string npcPath = Path.Combine(folderPath, S1NPC.SaveFolderName);
+ base.SaveInternal(npcPath, ref extraSaveables);
}
///
@@ -221,7 +204,7 @@ internal override void StartInternal()
/// The message you want the player to see. Unity rich text is allowed.
/// Instances of to display.
/// The delay between when the message is sent and when the player can reply.
- /// Whether or not this should propagate to all players.
+ /// Whether this should propagate to all players or not.
public void SendTextMessage(string message, Response[]? responses = null, float responseDelay = 1f, bool network = true)
{
S1NPC.SendTextMessage(message);
@@ -238,7 +221,6 @@ public void SendTextMessage(string message, Response[]? responses = null, float
{
Responses.Add(response);
responsesList.Add(response.S1Response);
- MelonLogger.Msg(response.Label);
}
S1NPC.MSGConversation.ShowResponses(
diff --git a/S1API/PhoneApp/MyAwesomeApp.cs b/S1API/PhoneApp/MyAwesomeApp.cs
new file mode 100644
index 00000000..2a5fa577
--- /dev/null
+++ b/S1API/PhoneApp/MyAwesomeApp.cs
@@ -0,0 +1,20 @@
+using UnityEngine;
+using UnityEngine.UI;
+using S1API.PhoneApp;
+
+namespace S1API.PhoneApp
+{
+ public class MyAwesomeApp : PhoneApp
+ {
+ protected override string AppName => "MyAwesomeApp";
+ protected override string AppTitle => "My Awesome App";
+ protected override string IconLabel => "Awesome";
+ protected override string IconFileName => "my_icon.png";
+
+ protected override void OnCreated(GameObject container)
+ {
+ GameObject panel = UIFactory.Panel("MainPanel", container.transform, Color.black);
+ UIFactory.Text("HelloText", "Hello from My Awesome App!", panel.transform, 22, TextAnchor.MiddleCenter, FontStyle.Bold);
+ }
+ }
+}
diff --git a/S1API/PhoneApp/PhoneApp.cs b/S1API/PhoneApp/PhoneApp.cs
new file mode 100644
index 00000000..9242e836
--- /dev/null
+++ b/S1API/PhoneApp/PhoneApp.cs
@@ -0,0 +1,250 @@
+#if IL2CPP
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Events;
+#elif MONO
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Events;
+#endif
+
+using System.Collections;
+using System.IO;
+using MelonLoader;
+using Object = UnityEngine.Object;
+using MelonLoader.Utils;
+
+namespace S1API.PhoneApp
+{
+ ///
+ /// Base class for defining in-game phone apps. Automatically clones the phone UI,
+ /// injects your app, and supports icon customization.
+ ///
+ public abstract class PhoneApp
+ {
+ ///
+ /// Reference to the player object in the scene.
+ ///
+ protected GameObject Player;
+
+ ///
+ /// The actual app panel instance in the phone UI.
+ ///
+ protected GameObject AppPanel;
+
+ ///
+ /// Whether the app panel was created by this instance.
+ ///
+ protected bool AppCreated;
+
+ ///
+ /// Tracks whether the app icon has been injected into the home screen.
+ ///
+ protected bool IconModified;
+
+ ///
+ /// Prevents double-initialization.
+ ///
+ protected bool InitializationStarted;
+
+ ///
+ /// Unique internal name for the app GameObject.
+ ///
+ protected abstract string AppName { get; }
+
+ ///
+ /// Title text displayed at the top of the app UI.
+ ///
+ protected abstract string AppTitle { get; }
+
+ ///
+ /// Label shown below the app icon on the phone home screen.
+ ///
+ protected abstract string IconLabel { get; }
+
+ ///
+ /// PNG filename for the app icon (must be placed in UserData folder).
+ ///
+ protected abstract string IconFileName { get; }
+
+ ///
+ /// Called after the app is created and a UI container is available.
+ /// Implement your custom UI here.
+ ///
+ /// The GameObject container inside the app panel.
+ protected abstract void OnCreated(GameObject container);
+
+ ///
+ /// Begins async setup of the app, including icon and panel creation.
+ /// Should only be called once per session.
+ ///
+ /// Logger to report errors and status.
+ public void Init(MelonLogger.Instance logger)
+ {
+ if (!InitializationStarted)
+ {
+ InitializationStarted = true;
+ MelonCoroutines.Start(DelayedInit(logger));
+ }
+ }
+
+ ///
+ /// Coroutine that delays setup to ensure all UI elements are ready.
+ ///
+ private IEnumerator DelayedInit(MelonLogger.Instance logger)
+ {
+ yield return new WaitForSeconds(5f);
+
+ Player = GameObject.Find("Player_Local");
+ if (Player == null)
+ {
+ logger.Error("Player_Local not found.");
+ yield break;
+ }
+
+ GameObject appsCanvas = GameObject.Find("Player_Local/CameraContainer/Camera/OverlayCamera/GameplayMenu/Phone/phone/AppsCanvas");
+ if (appsCanvas == null)
+ {
+ logger.Error("AppsCanvas not found.");
+ yield break;
+ }
+
+ Transform existingApp = appsCanvas.transform.Find(AppName);
+ if (existingApp != null)
+ {
+ AppPanel = existingApp.gameObject;
+ SetupExistingAppPanel(AppPanel, logger);
+ }
+ else
+ {
+ Transform templateApp = appsCanvas.transform.Find("ProductManagerApp");
+ if (templateApp == null)
+ {
+ logger.Error("Template ProductManagerApp not found.");
+ yield break;
+ }
+
+ AppPanel = Object.Instantiate(templateApp.gameObject, appsCanvas.transform);
+ AppPanel.name = AppName;
+
+ Transform containerTransform = AppPanel.transform.Find("Container");
+ if (containerTransform != null)
+ {
+ GameObject container = containerTransform.gameObject;
+ ClearContainer(container);
+ OnCreated(container);
+ }
+
+ AppCreated = true;
+ }
+
+ AppPanel.SetActive(false);
+
+ if (!IconModified)
+ {
+ IconModified = ModifyAppIcon(IconLabel, IconFileName, logger);
+ if (IconModified)
+ logger.Msg("Icon modified.");
+ }
+ }
+
+ ///
+ /// Sets up an existing app panel found in the scene (likely reused from a previous session).
+ ///
+ private void SetupExistingAppPanel(GameObject panel, MelonLogger.Instance logger)
+ {
+ Transform containerTransform = panel.transform.Find("Container");
+ if (containerTransform != null)
+ {
+ GameObject container = containerTransform.gameObject;
+ if (container.transform.childCount < 2)
+ {
+ ClearContainer(container);
+ // TODO: (@omar-akermi) Looks like a method got relabeled. Need to resolve :(
+ // BuildUI(container);
+ }
+ }
+
+ AppCreated = true;
+ }
+
+ ///
+ /// Destroys all children of the app container to prepare for UI rebuilding.
+ ///
+ private void ClearContainer(GameObject container)
+ {
+ for (int i = container.transform.childCount - 1; i >= 0; i--)
+ Object.Destroy(container.transform.GetChild(i).gameObject);
+ }
+
+ ///
+ /// Attempts to clone an app icon from the home screen and customize it.
+ ///
+ private bool ModifyAppIcon(string labelText, string fileName, MelonLogger.Instance logger)
+ {
+ GameObject parent = GameObject.Find("Player_Local/CameraContainer/Camera/OverlayCamera/GameplayMenu/Phone/phone/HomeScreen/AppIcons/");
+ if (parent == null)
+ {
+ logger?.Error("AppIcons not found.");
+ return false;
+ }
+
+ Transform lastIcon = parent.transform.childCount > 0 ? parent.transform.GetChild(parent.transform.childCount - 1) : null;
+ if (lastIcon == null)
+ {
+ logger?.Error("No icon found to clone.");
+ return false;
+ }
+
+ GameObject iconObj = lastIcon.gameObject;
+ iconObj.name = AppName;
+
+ Transform labelTransform = iconObj.transform.Find("Label");
+ Text label = labelTransform?.GetComponent();
+ if (label != null) label.text = labelText;
+
+ return ChangeAppIconImage(iconObj, fileName, logger);
+ }
+
+ ///
+ /// Loads the icon PNG from disk and applies it to the cloned icon.
+ ///
+ private bool ChangeAppIconImage(GameObject iconObj, string filename, MelonLogger.Instance logger)
+ {
+ Transform imageTransform = iconObj.transform.Find("Mask/Image");
+ Image image = imageTransform?.GetComponent();
+ if (image == null)
+ {
+ logger?.Error("Image component not found in icon.");
+ return false;
+ }
+
+ string path = Path.Combine(MelonEnvironment.UserDataDirectory, filename);
+ if (!File.Exists(path))
+ {
+ logger?.Error("Icon file not found: " + path);
+ return false;
+ }
+
+ try
+ {
+ byte[] bytes = File.ReadAllBytes(path);
+ Texture2D tex = new Texture2D(2, 2);
+
+ if (tex.LoadImage(bytes)) // IL2CPP-safe overload
+ {
+ image.sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f));
+ return true;
+ }
+
+ Object.Destroy(tex);
+ }
+ catch (System.Exception e)
+ {
+ logger?.Error("Failed to load image: " + e.Message);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/S1API/PhoneApp/PhoneAppManager.cs b/S1API/PhoneApp/PhoneAppManager.cs
new file mode 100644
index 00000000..073fef9e
--- /dev/null
+++ b/S1API/PhoneApp/PhoneAppManager.cs
@@ -0,0 +1,46 @@
+using System.Collections;
+using System.Collections.Generic;
+using MelonLoader;
+using UnityEngine;
+
+namespace S1API.PhoneApp
+{
+ ///
+ /// Central manager for spawning phone apps.
+ /// Usage: PhoneAppManager.Register(new MyCustomApp());
+ ///
+ public static class PhoneAppManager
+ {
+ private static readonly List registeredApps = new List();
+ private static bool initialized = false;
+
+ ///
+ /// Register your custom app. Should be called from OnApplicationStart().
+ ///
+ public static void Register(PhoneApp app)
+ {
+ registeredApps.Add(app);
+ }
+
+ ///
+ /// Call this once after the game scene is loaded.
+ /// Automatically initializes all registered apps.
+ ///
+ public static void InitAll(MelonLogger.Instance logger)
+ {
+ if (initialized) return;
+ initialized = true;
+ MelonCoroutines.Start(DelayedInitAll(logger));
+ }
+
+ private static IEnumerator DelayedInitAll(MelonLogger.Instance logger)
+ {
+ yield return new WaitForSeconds(5f);
+
+ foreach (var app in registeredApps)
+ {
+ app.Init(logger);
+ }
+ }
+ }
+}
diff --git a/S1API/PhoneApp/UIFactory.cs b/S1API/PhoneApp/UIFactory.cs
new file mode 100644
index 00000000..2e9a9d43
--- /dev/null
+++ b/S1API/PhoneApp/UIFactory.cs
@@ -0,0 +1,138 @@
+using UnityEngine;
+using UnityEngine.UI;
+
+public static class UIFactory
+{
+ ///
+ /// Creates a background panel with optional anchors.
+ ///
+ public static GameObject Panel(string name, Transform parent, Color bgColor, Vector2? anchorMin = null, Vector2? anchorMax = null, bool fullAnchor = false)
+ {
+ GameObject go = new GameObject(name);
+ go.transform.SetParent(parent, false);
+ RectTransform rt = go.AddComponent();
+
+ if (fullAnchor)
+ {
+ rt.anchorMin = Vector2.zero;
+ rt.anchorMax = Vector2.one;
+ rt.offsetMin = Vector2.zero;
+ rt.offsetMax = Vector2.zero;
+ }
+ else
+ {
+ rt.anchorMin = anchorMin ?? Vector2.zero;
+ rt.anchorMax = anchorMax ?? Vector2.one;
+ rt.offsetMin = Vector2.zero;
+ rt.offsetMax = Vector2.zero;
+ }
+
+ Image bg = go.AddComponent();
+ bg.color = bgColor;
+
+ return go;
+ }
+
+ ///
+ /// Creates a UI text element.
+ ///
+ public static Text Text(string name, string content, Transform parent, int fontSize = 16, TextAnchor anchor = TextAnchor.UpperLeft, FontStyle style = FontStyle.Normal)
+ {
+ GameObject go = new GameObject(name);
+ go.transform.SetParent(parent, false);
+
+ RectTransform rt = go.AddComponent();
+ rt.sizeDelta = new Vector2(0f, 30f);
+
+ Text txt = go.AddComponent();
+ txt.text = content;
+ txt.font = Resources.GetBuiltinResource("Arial.ttf");
+ txt.fontSize = fontSize;
+ txt.alignment = anchor;
+ txt.fontStyle = style;
+ txt.color = Color.white;
+
+ return txt;
+ }
+
+ ///
+ /// Creates a button with label and background color.
+ ///
+ public static GameObject Button(string name, string label, Transform parent, Color color)
+ {
+ GameObject buttonGO = new GameObject(name);
+ buttonGO.transform.SetParent(parent, false);
+
+ RectTransform rt = buttonGO.AddComponent();
+ rt.sizeDelta = new Vector2(0, 40);
+
+ Image img = buttonGO.AddComponent();
+ img.color = color;
+
+ Button btn = buttonGO.AddComponent
protected readonly QuestEntry[] QuestEntries = Array.Empty();
- [SaveableField("SOEQuest")]
- private QuestData _questData = new QuestData();
+ [SaveableField("QuestData")]
+ private readonly QuestData _questData;
- internal string? SaveFolder => _s1Quest?.SaveFolderName;
+ internal string? SaveFolder => S1Quest.SaveFolderName;
- internal S1Quests.Quest S1Quest => _s1Quest ?? throw new InvalidOperationException("S1Quest not initialized");
- private S1Quests.Quest? _s1Quest;
- private GameObject? _gameObject;
+ internal readonly S1Quests.Quest S1Quest;
+ private readonly GameObject _gameObject;
- internal override void InitializeInternal(GameObject gameObject, string guid = "")
+ ///
+ /// INTERNAL: Public constructor used for instancing the quest.
+ ///
+ public Quest()
{
- MelonLogger.Msg("Adding Quest Component...");
- _gameObject = gameObject;
- _s1Quest = gameObject.AddComponent();
- S1Quest.StaticGUID = guid;
+ _questData = new QuestData(GetType().Name);
+
+ _gameObject = new GameObject("Quest");
+ S1Quest = _gameObject.AddComponent();
+ S1Quest.StaticGUID = string.Empty;
S1Quest.onActiveState = new UnityEvent();
S1Quest.onComplete = new UnityEvent();
S1Quest.onInitialComplete = new UnityEvent();
@@ -76,8 +81,6 @@ internal override void InitializeInternal(GameObject gameObject, string guid = "
S1Quest.onTrackChange = new UnityEvent();
S1Quest.TrackOnBegin = true;
S1Quest.AutoCompleteOnAllEntriesComplete = true;
- // S1Quest.autoInitialize = false;
- MelonLogger.Msg("Assigning auto init...");
#if (MONO)
FieldInfo autoInitField = AccessTools.Field(typeof(S1Quests.Quest), "autoInitialize");
autoInitField.SetValue(S1Quest, false);
@@ -85,19 +88,17 @@ internal override void InitializeInternal(GameObject gameObject, string guid = "
S1Quest.autoInitialize = false;
#endif
- MelonLogger.Msg("Creating IconPrefab...");
// Setup quest icon prefab
GameObject iconPrefabObject = new GameObject("IconPrefab",
CrossType.Of(),
CrossType.Of(),
CrossType.Of()
);
- iconPrefabObject.transform.SetParent(gameObject.transform);
+ iconPrefabObject.transform.SetParent(_gameObject.transform);
Image iconImage = iconPrefabObject.GetComponent();
iconImage.sprite = S1Dev.PlayerSingleton.Instance.AppIcon;
S1Quest.IconPrefab = iconPrefabObject.GetComponent();
- MelonLogger.Msg("Creating PoIUIPrefab...");
// Setup UI for POI prefab
var uiPrefabObject = new GameObject("PoIUIPrefab",
CrossType.Of(),
@@ -105,9 +106,8 @@ internal override void InitializeInternal(GameObject gameObject, string guid = "
CrossType.Of(),
CrossType.Of