diff --git a/.github/workflows/deploy-build.yml b/.github/workflows/deploy-build.yml
index e91358e5..7655e5e4 100644
--- a/.github/workflows/deploy-build.yml
+++ b/.github/workflows/deploy-build.yml
@@ -56,18 +56,28 @@ jobs:
sed -i "s/{VERSION_NUMBER}/${{ steps.semantic-release.outputs.new_release_version }}/" ./S1API/S1API.cs
sed -i "s/{VERSION_NUMBER}/${{ steps.semantic-release.outputs.new_release_version }}/" ./S1APILoader/S1APILoader.cs
- - name: Run .NET build for Mono
- run: dotnet build ./S1API.sln -c Mono -f netstandard2.1
+# TODO (@MaxtorCoder): Temporarily disabling until BepInEx is building properly locally.
+# - name: Run .NET build for MonoBepInEx
+# run: dotnet build ./S1API.sln -c MonoBepInEx -f netstandard2.1
+#
+# - name: Run .NET build for Il2CppBepInEx
+# run: dotnet build ./S1API.sln -c Il2CppBepInEx -f net6.0
- - name: Run .NET build for Il2Cpp
- run: dotnet build ./S1API.sln -c Il2Cpp -f netstandard2.1
+ - name: Run .NET build for MonoMelon
+ run: dotnet build ./S1API.sln -c MonoMelon -f netstandard2.1
+
+ - name: Run .NET build for Il2CppMelon
+ run: dotnet build ./S1API.sln -c Il2CppMelon -f net6.0
- name: Build artifact zip for Thunderstore
run: |
mkdir -p ./artifacts/thunderstore/Plugins/S1API
- cp ./S1APILoader/bin/Mono/netstandard2.1/S1APILoader.dll ./artifacts/thunderstore/Plugins/S1APILoader.dll
- cp ./S1API/bin/Il2Cpp/netstandard2.1/S1API.dll ./artifacts/thunderstore/Plugins/S1API/S1API.Il2Cpp.dll
- cp ./S1API/bin/Mono/netstandard2.1/S1API.dll ./artifacts/thunderstore/Plugins/S1API/S1API.Mono.dll
+ cp ./S1APILoader/bin/MonoMelon/netstandard2.1/S1APILoader.dll ./artifacts/thunderstore/Plugins/S1APILoader.dll
+# cp ./S1APILoader/bin/MonoBepInEx/netstandard2.1/S1APILoader.dll ./artifacts/thunderstore/Plugins/S1APILoader.BepInEx.dll
+ cp ./S1API/bin/Il2CppMelon/net6.0/S1API.dll ./artifacts/thunderstore/Mods/S1API.Il2Cpp.MelonLoader.dll
+ cp ./S1API/bin/MonoMelon/netstandard2.1/S1API.dll ./artifacts/thunderstore/Mods/S1API.Mono.MelonLoader.dll
+# cp ./S1API/bin/Il2CppMelon/net6.0/S1API.dll ./artifacts/thunderstore/Plugins/S1API/S1API.Il2Cpp.BepInEx.dll
+# cp ./S1API/bin/MonoMelon/netstandard2.1/S1API.dll ./artifacts/thunderstore/Plugins/S1API/S1API.Mono.BepInEx.dll
- name: Publish artifact to Thunderstore
uses: GreenTF/upload-thunderstore-package@v4.3
diff --git a/.github/workflows/verify-build.yml b/.github/workflows/verify-build.yml
index 99706f0f..1cdef91b 100644
--- a/.github/workflows/verify-build.yml
+++ b/.github/workflows/verify-build.yml
@@ -27,9 +27,16 @@ jobs:
- name: Restore .NET Dependencies
run: dotnet restore
-
- - name: Run .NET Build for Mono
- run: dotnet build ./S1API.sln -c Mono -f netstandard2.1
-
- - name: Run .NET Build for Il2Cpp
- run: dotnet build ./S1API.sln -c Il2Cpp -f netstandard2.1
\ No newline at end of file
+
+# TODO (@MaxtorCoder): Temporarily disabling until BepInEx is building properly locally.
+# - name: Run .NET build for MonoBepInEx
+# run: dotnet build ./S1API.sln -c MonoBepInEx -f netstandard2.1
+#
+# - name: Run .NET build for Il2CppBepInEx
+# run: dotnet build ./S1API.sln -c Il2CppBepInEx -f net6.0
+
+ - name: Run .NET build for MonoMelon
+ run: dotnet build ./S1API.sln -c MonoMelon -f netstandard2.1
+
+ - name: Run .NET build for Il2CppMelon
+ run: dotnet build ./S1API.sln -c Il2CppMelon -f net6.0
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index cf6412f6..4b329393 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
obj/
bin/
*.user
+.vs/
# Build props
local.*
diff --git a/S1API.sln b/S1API.sln
index b923963a..72b17c00 100644
--- a/S1API.sln
+++ b/S1API.sln
@@ -6,17 +6,27 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S1APILoader", "S1APILoader\
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Mono|Any CPU = Mono|Any CPU
- Il2Cpp|Any CPU = Il2Cpp|Any CPU
+ MonoBepInEx|Any CPU = MonoBepInEx|Any CPU
+ Il2CppMelon|Any CPU = Il2CppMelon|Any CPU
+ MonoMelon|Any CPU = MonoMelon|Any CPU
+ Il2CppBepInEx|Any CPU = Il2CppBepInEx|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.Mono|Any CPU.ActiveCfg = Mono|Any CPU
- {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.Mono|Any CPU.Build.0 = Mono|Any CPU
- {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.Il2Cpp|Any CPU.ActiveCfg = Il2Cpp|Any CPU
- {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.Il2Cpp|Any CPU.Build.0 = Il2Cpp|Any CPU
- {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.Mono|Any CPU.ActiveCfg = Mono|Any CPU
- {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.Mono|Any CPU.Build.0 = Mono|Any CPU
- {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.Il2Cpp|Any CPU.ActiveCfg = Il2Cpp|Any CPU
- {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.Il2Cpp|Any CPU.Build.0 = Il2Cpp|Any CPU
+ {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.MonoBepInEx|Any CPU.ActiveCfg = MonoBepInEx|Any CPU
+ {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.MonoBepInEx|Any CPU.Build.0 = MonoBepInEx|Any CPU
+ {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.Il2CppMelon|Any CPU.ActiveCfg = Il2CppMelon|Any CPU
+ {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.Il2CppMelon|Any CPU.Build.0 = Il2CppMelon|Any CPU
+ {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.MonoMelon|Any CPU.ActiveCfg = MonoMelon|Any CPU
+ {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.MonoMelon|Any CPU.Build.0 = MonoMelon|Any CPU
+ {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.Il2CppBepInEx|Any CPU.ActiveCfg = Il2CppBepInEx|Any CPU
+ {D2C9A6B1-B9E0-4E6D-AE65-B5264C2A4E04}.Il2CppBepInEx|Any CPU.Build.0 = Il2CppBepInEx|Any CPU
+ {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.MonoBepInEx|Any CPU.ActiveCfg = MonoBepInEx|Any CPU
+ {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.MonoBepInEx|Any CPU.Build.0 = MonoBepInEx|Any CPU
+ {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.Il2CppMelon|Any CPU.ActiveCfg = Il2CppMelon|Any CPU
+ {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.Il2CppMelon|Any CPU.Build.0 = Il2CppMelon|Any CPU
+ {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.MonoMelon|Any CPU.ActiveCfg = MonoMelon|Any CPU
+ {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.MonoMelon|Any CPU.Build.0 = MonoMelon|Any CPU
+ {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.Il2CppBepInEx|Any CPU.ActiveCfg = Il2CppBepInEx|Any CPU
+ {B97277C2-27FE-4BB9-AB5A-D479C8DF6827}.Il2CppBepInEx|Any CPU.Build.0 = Il2CppBepInEx|Any CPU
EndGlobalSection
EndGlobal
diff --git a/S1API/Console/ConsoleHelper.cs b/S1API/Console/ConsoleHelper.cs
index 60e54d82..f1239090 100644
--- a/S1API/Console/ConsoleHelper.cs
+++ b/S1API/Console/ConsoleHelper.cs
@@ -1,15 +1,16 @@
-using System.Collections.Generic;
-
-#if IL2CPP
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using Il2CppSystem.Collections.Generic;
using static Il2CppScheduleOne.Console;
-
#else
using static ScheduleOne.Console;
+using System.Collections.Generic;
#endif
-namespace S1API.Utils
+namespace S1API.Console
{
+ ///
+ /// This class provides easy access to the in-game console system.
+ ///
public static class ConsoleHelper
{
///
@@ -19,7 +20,7 @@ public static class ConsoleHelper
/// The cash amount to add or remove.
public static void RunCashCommand(int amount)
{
-#if IL2CPP
+#if (IL2CPPMELON || IL2CPPBEPINEX)
var command = new ChangeCashCommand();
var args = new Il2CppSystem.Collections.Generic.List();
#else
diff --git a/S1API/DeadDrops/DeadDropInstance.cs b/S1API/DeadDrops/DeadDropInstance.cs
index ee49c249..91c57942 100644
--- a/S1API/DeadDrops/DeadDropInstance.cs
+++ b/S1API/DeadDrops/DeadDropInstance.cs
@@ -1,6 +1,6 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1Economy = Il2CppScheduleOne.Economy;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1Economy = ScheduleOne.Economy;
#endif
@@ -59,4 +59,4 @@ internal DeadDropInstance(S1Economy.DeadDrop deadDrop) =>
public Vector3 Position =>
S1DeadDrop.transform.position;
}
-}
\ No newline at end of file
+}
diff --git a/S1API/DeadDrops/DeadDropManager.cs b/S1API/DeadDrops/DeadDropManager.cs
index edf1345c..e162f337 100644
--- a/S1API/DeadDrops/DeadDropManager.cs
+++ b/S1API/DeadDrops/DeadDropManager.cs
@@ -1,6 +1,6 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1Economy = Il2CppScheduleOne.Economy;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1Economy = ScheduleOne.Economy;
#endif
@@ -19,4 +19,4 @@ public class DeadDropManager
public static DeadDropInstance[] All =>
S1Economy.DeadDrop.DeadDrops.ToArray()
.Select(deadDrop => new DeadDropInstance(deadDrop)).ToArray(); }
-}
\ No newline at end of file
+}
diff --git a/S1API/Entities/Interfaces/IEntity.cs b/S1API/Entities/Interfaces/IEntity.cs
new file mode 100644
index 00000000..cc551b10
--- /dev/null
+++ b/S1API/Entities/Interfaces/IEntity.cs
@@ -0,0 +1,25 @@
+using UnityEngine;
+
+namespace S1API.Entities.Interfaces
+{
+ ///
+ /// Represents an entity within the game world.
+ ///
+ public interface IEntity
+ {
+ ///
+ /// INTERNAL: Tracking of the GameObject associated with this entity.
+ ///
+ GameObject gameObject { get; }
+
+ ///
+ /// The world position of the entity.
+ ///
+ public Vector3 Position { get; set; }
+
+ ///
+ /// The scale of the entity.
+ ///
+ public float Scale { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/Interfaces/IHealth.cs b/S1API/Entities/Interfaces/IHealth.cs
new file mode 100644
index 00000000..2be07bf1
--- /dev/null
+++ b/S1API/Entities/Interfaces/IHealth.cs
@@ -0,0 +1,57 @@
+using System;
+
+namespace S1API.Entities.Interfaces
+{
+ ///
+ /// Represents an entity that has health associated.
+ ///
+ public interface IHealth
+ {
+ ///
+ /// The current health of the entity.
+ ///
+ public float CurrentHealth { get; }
+
+ ///
+ /// The max health of the entity.
+ ///
+ public float MaxHealth { get; set; }
+
+ ///
+ /// Whether the entity is dead or not.
+ ///
+ public bool IsDead { get; }
+
+ ///
+ /// Whether the entity is invincible.
+ ///
+ public bool IsInvincible { get; set; }
+
+ ///
+ /// Revives the entity.
+ ///
+ public void Revive();
+
+ ///
+ /// Deals damage to the entity.
+ ///
+ /// Amount of damage to deal.
+ public void Damage(int amount);
+
+ ///
+ /// Heals the entity.
+ ///
+ /// Amount of health to heal.
+ public void Heal(int amount);
+
+ ///
+ /// Kills the entity.
+ ///
+ public void Kill();
+
+ ///
+ /// Called when entity's health is less than or equal to 0.
+ ///
+ public event Action OnDeath;
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPC.cs b/S1API/Entities/NPC.cs
new file mode 100644
index 00000000..6cfea9e9
--- /dev/null
+++ b/S1API/Entities/NPC.cs
@@ -0,0 +1,632 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using S1DevUtilities = Il2CppScheduleOne.DevUtilities;
+using S1Interaction = Il2CppScheduleOne.Interaction;
+using S1Messaging = Il2CppScheduleOne.Messaging;
+using S1Noise = Il2CppScheduleOne.Noise;
+using S1Relation = Il2CppScheduleOne.NPCs.Relation;
+using S1Responses = Il2CppScheduleOne.NPCs.Responses;
+using S1PlayerScripts = Il2CppScheduleOne.PlayerScripts;
+using S1ContactApps = Il2CppScheduleOne.UI.Phone.ContactsApp;
+using S1WorkspacePopup = Il2CppScheduleOne.UI.WorldspacePopup;
+using S1AvatarFramework = Il2CppScheduleOne.AvatarFramework;
+using S1Behaviour = Il2CppScheduleOne.NPCs.Behaviour;
+using S1Vehicles = Il2CppScheduleOne.Vehicles;
+using S1Vision = Il2CppScheduleOne.Vision;
+using S1NPCs = Il2CppScheduleOne.NPCs;
+using Il2CppSystem.Collections.Generic;
+#elif (MONOMELON || MONOBEPINEX)
+using S1DevUtilities = ScheduleOne.DevUtilities;
+using S1Interaction = ScheduleOne.Interaction;
+using S1Messaging = ScheduleOne.Messaging;
+using S1Noise = ScheduleOne.Noise;
+using S1Relation = ScheduleOne.NPCs.Relation;
+using S1Responses = ScheduleOne.NPCs.Responses;
+using S1PlayerScripts = ScheduleOne.PlayerScripts;
+using S1ContactApps = ScheduleOne.UI.Phone.ContactsApp;
+using S1WorkspacePopup = ScheduleOne.UI.WorldspacePopup;
+using S1AvatarFramework = ScheduleOne.AvatarFramework;
+using S1Behaviour = ScheduleOne.NPCs.Behaviour;
+using S1Vehicles = ScheduleOne.Vehicles;
+using S1Vision = ScheduleOne.Vision;
+using S1NPCs = ScheduleOne.NPCs;
+using System.Collections.Generic;
+#endif
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using HarmonyLib;
+using S1API.Entities.Interfaces;
+using S1API.Internal.Abstraction;
+using S1API.Map;
+using S1API.Messaging;
+using UnityEngine;
+using UnityEngine.Events;
+
+namespace S1API.Entities
+{
+ ///
+ /// An abstract class intended to be derived from for creating custom NPCs in the game.
+ ///
+ public abstract class NPC : Saveable, IEntity, IHealth
+ {
+ // Protected members intended to be used by modders.
+ // Intended to be used from within the class / derived classes ONLY.
+ #region Protected Members
+
+ ///
+ /// A list of text responses you've added to your NPC.
+ ///
+ protected readonly System.Collections.Generic.List Responses = new System.Collections.Generic.List();
+
+ ///
+ /// Base constructor for a new NPC.
+ /// Not intended for instancing your NPC!
+ /// Instead, create your derived class and let S1API handle instancing.
+ ///
+ /// Unique identifier for your NPC.
+ /// The first name for your NPC.
+ /// The last name for your NPC.
+ /// The icon for your NPC for messages, realationships, etc.
+ protected NPC(
+ string id,
+ string? firstName,
+ string? lastName,
+ Sprite? icon = null
+ )
+ {
+ IsCustomNPC = true;
+ gameObject = new GameObject();
+
+ // 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.MugshotSprite = icon ?? S1DevUtilities.PlayerSingleton.Instance.AppIcon;
+ S1NPC.BakedGUID = Guid.NewGuid().ToString();
+
+ // ReSharper disable once UseObjectOrCollectionInitializer (IL2CPP COMPAT)
+ S1NPC.ConversationCategories = new List();
+ S1NPC.ConversationCategories.Add(S1Messaging.EConversationCategory.Customer);
+
+ // Create our MessageConversation
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+ S1NPC.CreateMessageConversation();
+#elif (MONOMELON || MONOBEPINEX)
+ MethodInfo createConvoMethod = AccessTools.Method(typeof(S1NPCs.NPC), "CreateMessageConversation");
+ createConvoMethod.Invoke(S1NPC, null);
+#endif
+
+ // Add UnityEvents for NPCHealth
+ S1NPC.Health = gameObject.GetComponent();
+ S1NPC.Health.onDie = new UnityEvent();
+ S1NPC.Health.onKnockedOut = new UnityEvent();
+ S1NPC.Health.Invincible = true;
+ S1NPC.Health.MaxHealth = 100f;
+
+ // Awareness behaviour
+ GameObject awarenessObject = new GameObject("NPCAwareness");
+ awarenessObject.transform.SetParent(gameObject.transform);
+ S1NPC.awareness = awarenessObject.AddComponent();
+ S1NPC.awareness.onExplosionHeard = new UnityEvent();
+ S1NPC.awareness.onGunshotHeard = new UnityEvent();
+ S1NPC.awareness.onHitByCar = new UnityEvent();
+ S1NPC.awareness.onNoticedDrugDealing = new UnityEvent();
+ S1NPC.awareness.onNoticedGeneralCrime = new UnityEvent();
+ S1NPC.awareness.onNoticedPettyCrime = new UnityEvent();
+ S1NPC.awareness.onNoticedPlayerViolatingCurfew = new UnityEvent();
+ S1NPC.awareness.onNoticedSuspiciousPlayer = new UnityEvent();
+ S1NPC.awareness.Listener = gameObject.AddComponent();
+
+ /////// START BEHAVIOUR CODE ////////
+ // NPCBehaviours behaviour
+ GameObject behaviourObject = new GameObject("NPCBehaviour");
+ behaviourObject.transform.SetParent(gameObject.transform);
+ S1Behaviour.NPCBehaviour behaviour = behaviourObject.AddComponent();
+
+ GameObject cowingBehaviourObject = new GameObject("CowingBehaviour");
+ cowingBehaviourObject.transform.SetParent(behaviourObject.transform);
+ S1Behaviour.CoweringBehaviour coweringBehaviour = cowingBehaviourObject.AddComponent();
+
+ GameObject fleeBehaviourObject = new GameObject("FleeBehaviour");
+ fleeBehaviourObject.transform.SetParent(behaviourObject.transform);
+ S1Behaviour.FleeBehaviour fleeBehaviour = fleeBehaviourObject.AddComponent();
+
+ behaviour.CoweringBehaviour = coweringBehaviour;
+ behaviour.FleeBehaviour = fleeBehaviour;
+ S1NPC.behaviour = behaviour;
+ /////// END BEHAVIOUR CODE ////////
+
+ // Response to actions like gunshots, drug deals, etc.
+ GameObject responsesObject = new GameObject("NPCResponses");
+ responsesObject.transform.SetParent(gameObject.transform);
+ S1NPC.awareness.Responses = responsesObject.AddComponent();
+
+ // Vision cone object and behaviour
+ GameObject visionObject = new GameObject("VisionCone");
+ visionObject.transform.SetParent(gameObject.transform);
+ S1Vision.VisionCone visionCone = visionObject.AddComponent();
+ visionCone.StatesOfInterest.Add(new S1Vision.VisionCone.StateContainer
+ {
+ state = S1PlayerScripts.PlayerVisualState.EVisualState.PettyCrime, RequiredNoticeTime = 0.1f
+ });
+ S1NPC.awareness.VisionCone = visionCone;
+
+
+ // Suspicious ? icon in world space
+ S1NPC.awareness.VisionCone.QuestionMarkPopup = gameObject.AddComponent();
+
+ // Interaction behaviour
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+ S1NPC.intObj = gameObject.AddComponent();
+#elif (MONOMELON || MONOBEPINEX)
+ FieldInfo intObjField = AccessTools.Field(typeof(S1NPCs.NPC), "intObj");
+ intObjField.SetValue(S1NPC, gameObject.AddComponent());
+#endif
+
+ // Relationship data
+ S1NPC.RelationData = new S1Relation.NPCRelationData();
+
+ // Inventory behaviour
+ S1NPCs.NPCInventory inventory = gameObject.AddComponent();
+
+ // Pickpocket behaviour
+ inventory.PickpocketIntObj = gameObject.AddComponent();
+
+ // Defaulting to the local player for Avatar TODO: Change
+ S1NPC.Avatar = S1AvatarFramework.MugshotGenerator.Instance.MugshotRig;
+
+ // Enable our custom gameObjects so they can initialize
+ gameObject.SetActive(true);
+
+ All.Add(this);
+ }
+
+ ///
+ /// Called when a response is loaded from the save file.
+ /// Override this method for attaching your callbacks to your methods.
+ ///
+ /// The response that was loaded.
+ protected virtual void OnResponseLoaded(Response response) { }
+
+ #endregion
+
+ // Public members intended to be used by modders.
+ // Can be used inside your derived class, or outside via instance reference.
+ #region Public Members
+
+ ///
+ /// INTERNAL: Tracking for the GameObject associated with this NPC.
+ /// Not intended for use by modders!
+ ///
+ public GameObject gameObject { get; }
+
+ ///
+ /// The world position of the NPC.
+ ///
+ public Vector3 Position
+ {
+ get => gameObject.transform.position;
+ set => S1NPC.Movement.Warp(value);
+ }
+
+ ///
+ /// The transform of the NPC.
+ /// Please do not set the properties of this transform.
+ ///
+ public Transform Transform =>
+ gameObject.transform;
+
+ ///
+ /// List of all NPCs within the base game and modded.
+ ///
+ public static readonly System.Collections.Generic.List All = new System.Collections.Generic.List();
+
+ ///
+ /// The first name of this NPC.
+ ///
+ public string FirstName
+ {
+ get => S1NPC.FirstName;
+ set => S1NPC.FirstName = value;
+ }
+
+ ///
+ /// The last name of this NPC.
+ ///
+ public string LastName
+ {
+ get => S1NPC.LastName;
+ set => S1NPC.LastName = value;
+ }
+
+ ///
+ /// The full name of this NPC.
+ /// If there is no last name, it will just return the first name.
+ ///
+ public string FullName =>
+ S1NPC.fullName;
+
+ ///
+ /// The unique identifier to assign to this NPC.
+ /// Used when saving and loading. Probably other things within the base game code.
+ ///
+ public string ID
+ {
+ get => S1NPC.ID;
+ protected set => S1NPC.ID = value;
+ }
+
+ ///
+ /// The icon assigned to this NPC.
+ ///
+ public Sprite Icon
+ {
+ get => S1NPC.MugshotSprite;
+ set => S1NPC.MugshotSprite = value;
+ }
+
+ ///
+ /// Whether the NPC is currently conscious or not.
+ ///
+ public bool IsConscious =>
+ S1NPC.IsConscious;
+
+ ///
+ /// Whether the NPC is currently inside a building or not.
+ ///
+ public bool IsInBuilding =>
+ S1NPC.isInBuilding;
+
+ ///
+ /// Whether the NPC is currently inside a vehicle or not.
+ ///
+ public bool IsInVehicle =>
+ S1NPC.IsInVehicle;
+
+ ///
+ /// Whether the NPC is currently panicking or not.
+ ///
+ public bool IsPanicking =>
+ S1NPC.IsPanicked;
+
+ ///
+ /// Whether the NPC is currently unsettled or not.
+ ///
+ public bool IsUnsettled =>
+ S1NPC.isUnsettled;
+
+ ///
+ /// UNCONFIRMED: Whether the NPC is currently visible to the player or not.
+ /// If you confirm this, please let us know so we can update the documentation!
+ ///
+ public bool IsVisible =>
+ S1NPC.isVisible;
+
+ ///
+ /// How aggressive this NPC is towards others.
+ ///
+ public float Aggressiveness
+ {
+ get => S1NPC.Aggression;
+ set => S1NPC.Aggression = value;
+ }
+
+ ///
+ /// The region the NPC is associated with.
+ /// Note: Not the region they're in currently. Just the region they're designated to.
+ ///
+ public Region Region =>
+ (Region)S1NPC.Region;
+
+ ///
+ /// UNCONFIRMED: How long the NPC will panic for.
+ /// If you confirm this, please let us know so we can update the documentation!
+ ///
+ public float PanicDuration
+ {
+ get => (float)_panicField.GetValue(S1NPC)!;
+ set => _panicField.SetValue(S1NPC, value);
+ }
+
+ ///
+ /// Sets the scale of the NPC.
+ ///
+ public float Scale
+ {
+ get => S1NPC.Scale;
+ set => S1NPC.SetScale(value);
+ }
+
+ ///
+ /// Whether the NPC is knocked out or not.
+ ///
+ public bool IsKnockedOut =>
+ S1NPC.Health.IsKnockedOut;
+
+ ///
+ /// UNCONFIRMED: Whether the NPC requires the region unlocked in order to deal to.
+ /// If you confirm this, please let us know so we can update the documentation!
+ ///
+ public bool RequiresRegionUnlocked
+ {
+ get => (bool)_requiresRegionUnlockedField.GetValue(S1NPC)!;
+ set => _panicField.SetValue(S1NPC, value);
+ }
+
+ // TODO: Add CurrentBuilding (currently missing NPCEnterableBuilding abstraction)
+ // public ??? CurrentBuilding { get; set; }
+
+ // TODO: Add CurrentVehicle (currently missing LandVehicle abstraction)
+ // public ??? CurrentVehicle { get; set; }
+
+ // TODO: Add Inventory (currently missing NPCInventory abstraction)
+ // public ??? Inventory { get; set; }
+
+ ///
+ /// The current health the NPC has.
+ ///
+ public float CurrentHealth =>
+ S1NPC.Health.Health;
+
+ ///
+ /// The maximum health the NPC has.
+ ///
+ public float MaxHealth
+ {
+ get => S1NPC.Health.MaxHealth;
+ set => S1NPC.Health.MaxHealth = value;
+ }
+
+ ///
+ /// Whether the NPC is dead or not.
+ ///
+ public bool IsDead =>
+ S1NPC.Health.IsDead;
+
+ ///
+ /// Whether the NPC is invincible or not.
+ ///
+ public bool IsInvincible
+ {
+ get => S1NPC.Health.Invincible;
+ set => S1NPC.Health.Invincible = value;
+ }
+
+ ///
+ /// Revives the NPC.
+ ///
+ public void Revive() =>
+ S1NPC.Health.Revive();
+
+ ///
+ /// Deals damage to the NPC.
+ ///
+ /// The amount of damage to deal.
+ public void Damage(int amount)
+ {
+ if (amount <= 0)
+ return;
+
+ S1NPC.Health.TakeDamage(amount, true);
+ }
+
+ ///
+ /// Heals the NPC.
+ ///
+ /// The amount of health to heal.
+ public void Heal(int amount)
+ {
+ if (amount <= 0)
+ return;
+
+ float actualHealAmount = Mathf.Min(amount, S1NPC.Health.MaxHealth - S1NPC.Health.Health);
+ S1NPC.Health.TakeDamage(-actualHealAmount, false);
+ }
+
+ ///
+ /// Kills the NPC.
+ ///
+ public void Kill() =>
+ S1NPC.Health.TakeDamage(S1NPC.Health.MaxHealth);
+
+ ///
+ /// Causes the NPC to become unsettled.
+ /// UNCONFIRMED: Will panic them for PanicDuration amount of time.
+ /// If you confirm this, please let us know so we can update the documentation!
+ ///
+ /// Length of time they should stay unsettled.
+ public void Unsettle(float duration) =>
+ _unsettleMethod.Invoke(S1NPC, new object[] { duration });
+
+ ///
+ /// Smoothly scales the NPC over lerpTime.
+ ///
+ /// The scale you want set.
+ /// The time to scale over.
+ public void LerpScale(float scale, float lerpTime) =>
+ S1NPC.SetScale(scale, lerpTime);
+
+ ///
+ /// Causes the NPC to become panicked.
+ ///
+ public void Panic() =>
+ S1NPC.SetPanicked();
+
+ ///
+ /// Causes the NPC to stop panicking, if they are currently.
+ ///
+ public void StopPanicking() =>
+ _removePanicMethod.Invoke(S1NPC, new object[] { });
+
+ ///
+ /// Knocks the NPC out.
+ /// NOTE: Does not work for invincible NPCs.
+ ///
+ public void KnockOut() =>
+ S1NPC.Health.KnockOut();
+
+ ///
+ /// Tells the NPC to travel to a specific position in world space.
+ ///
+ /// The position to travel to.
+ public void Goto(Vector3 position) =>
+ S1NPC.Movement.SetDestination(position);
+
+ // TODO: Add OnEnterVehicle listener (currently missing LandVehicle abstraction)
+ // public event Action OnEnterVehicle { }
+
+ // TODO: Add OnExitVehicle listener (currently missing LandVehicle abstraction)
+ // public event Action OnExitVehicle { }
+
+ // TODO: Add OnExplosionHeard listener (currently missing NoiseEvent abstraction)
+ // public event Action OnExplosionHeard { }
+
+ // TODO: Add OnGunshotHeard listener (currently missing NoiseEvent abstraction)
+ // public event Action OnGunshotHeard { }
+
+ // TODO: Add OnHitByCar listener (currently missing LandVehicle abstraction)
+ // public event Action OnHitByCar { }
+
+ // TODO: Add OnNoticedDrugDealing listener (currently missing Player abstraction)
+ // public event Action OnNoticedDrugDealing { }
+
+ // TODO: Add OnNoticedGeneralCrime listener (currently missing Player abstraction)
+ // public event Action OnNoticedGeneralCrime { }
+
+ // TODO: Add OnNoticedPettyCrime listener (currently missing Player abstraction)
+ // public event Action OnNoticedPettyCrime { }
+
+ // TODO: Add OnPlayerViolatingCurfew listener (currently missing Player abstraction)
+ // public event Action OnPlayerViolatingCurfew { }
+
+ // TODO: Add OnNoticedSuspiciousPlayer listener (currently missing Player abstraction)
+ // public event Action OnNoticedSuspiciousPlayer { }
+
+ ///
+ /// Called when the NPC died.
+ ///
+ public event Action OnDeath
+ {
+ add => EventHelper.AddListener(value, S1NPC.Health.onDie);
+ remove => EventHelper.RemoveListener(value, S1NPC.Health.onDie);
+ }
+
+ ///
+ /// Called when the NPC's inventory contents change.
+ ///
+ public event Action OnInventoryChanged
+ {
+ add => EventHelper.AddListener(value, S1NPC.Inventory.onContentsChanged);
+ remove => EventHelper.RemoveListener(value, S1NPC.Inventory.onContentsChanged);
+ }
+
+ ///
+ /// Sends a text message from this NPC to the players.
+ /// Supports responses with callbacks for additional logic.
+ ///
+ /// 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 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);
+ S1NPC.MSGConversation.ClearResponses();
+
+ if (responses == null || responses.Length == 0)
+ return;
+
+ Responses.Clear();
+
+ List responsesList = new List();
+
+ foreach (Response response in responses)
+ {
+ Responses.Add(response);
+ responsesList.Add(response.S1Response);
+ }
+
+ S1NPC.MSGConversation.ShowResponses(
+ responsesList,
+ responseDelay,
+ network
+ );
+ }
+
+ ///
+ /// Gets the instance of an NPC.
+ /// Supports base NPCs as well as other mod NPCs.
+ /// For base NPCs, .
+ ///
+ /// The NPC class to get the instance of.
+ ///
+ public static NPC? Get() =>
+ All.FirstOrDefault(npc => npc.GetType() == typeof(T));
+
+ #endregion
+
+ // Internal members used by S1API.
+ // Please do not attempt to use these members!
+ #region Internal Members
+
+ ///
+ /// INTERNAL: Reference to the NPC on the S1 side.
+ ///
+ internal readonly S1NPCs.NPC S1NPC;
+
+ ///
+ /// INTERNAL: Constructor used for base game NPCs.
+ ///
+ /// Reference to a base game NPC.
+ internal NPC(S1NPCs.NPC npc)
+ {
+ S1NPC = npc;
+ gameObject = npc.gameObject;
+ IsCustomNPC = false;
+ All.Add(this);
+ }
+
+ ///
+ /// 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)
+ {
+ Response response = new Response(s1Response) { Label = s1Response.label, Text = s1Response.text };
+ Responses.Add(response);
+ OnResponseLoaded(response);
+ }
+
+ base.CreateInternal();
+ }
+
+ internal override void SaveInternal(string folderPath, ref List extraSaveables)
+ {
+ string npcPath = Path.Combine(folderPath, S1NPC.SaveFolderName);
+ base.SaveInternal(npcPath, ref extraSaveables);
+ }
+ #endregion
+
+ // Private members used by the NPC class.
+ // Please do not attempt to use these members!
+ #region Private Members
+
+ internal readonly bool IsCustomNPC;
+
+ private readonly FieldInfo _panicField = AccessTools.Field(typeof(S1NPCs.NPC), "PANIC_DURATION");
+ private readonly FieldInfo _requiresRegionUnlockedField = AccessTools.Field(typeof(S1NPCs.NPC), "RequiresRegionUnlocked");
+
+ private readonly MethodInfo _unsettleMethod = AccessTools.Method(typeof(S1NPCs.NPC), "SetUnsettled");
+ private readonly MethodInfo _removePanicMethod = AccessTools.Method(typeof(S1NPCs.NPC), "RemovePanicked");
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/DanSamwell.cs b/S1API/Entities/NPCs/DanSamwell.cs
new file mode 100644
index 00000000..45170f06
--- /dev/null
+++ b/S1API/Entities/NPCs/DanSamwell.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs
+{
+ ///
+ /// UNCONFIRMED: Dan Samwell is a customer.
+ /// He is the NPC that owns Dan's Hardware!
+ /// If you confirm this, please let us know so we can update the documentation!
+ ///
+ public class DanSamwell : NPC
+ {
+ internal DanSamwell() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "dan_samwell")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Docks/AnnaChesterfield.cs b/S1API/Entities/NPCs/Docks/AnnaChesterfield.cs
new file mode 100644
index 00000000..8941322d
--- /dev/null
+++ b/S1API/Entities/NPCs/Docks/AnnaChesterfield.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Docks
+{
+ ///
+ /// Anna Chesterfield is a customer.
+ /// She lives in the Docks region.
+ /// Anna also works at the Barbershop.
+ ///
+ public class AnnaChesterfield : NPC
+ {
+ internal AnnaChesterfield() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "anna_chesterfield")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Docks/BillyKramer.cs b/S1API/Entities/NPCs/Docks/BillyKramer.cs
new file mode 100644
index 00000000..4c103ba5
--- /dev/null
+++ b/S1API/Entities/NPCs/Docks/BillyKramer.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Docks
+{
+ ///
+ /// Billy Kramer is a customer.
+ /// He lives in the Docks region.
+ ///
+ public class BillyKramer : NPC
+ {
+ internal BillyKramer() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "billy_kramer")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Docks/CrankyFrank.cs b/S1API/Entities/NPCs/Docks/CrankyFrank.cs
new file mode 100644
index 00000000..d8bbd5db
--- /dev/null
+++ b/S1API/Entities/NPCs/Docks/CrankyFrank.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Docks
+{
+ ///
+ /// Cranky Frank is a customer.
+ /// He lives in the Docks region.
+ /// Frank is the NPC with a pot on his head!
+ ///
+ public class CrankyFrank : NPC
+ {
+ internal CrankyFrank() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "cranky_frank")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Docks/GenghisBarn.cs b/S1API/Entities/NPCs/Docks/GenghisBarn.cs
new file mode 100644
index 00000000..0a015cf0
--- /dev/null
+++ b/S1API/Entities/NPCs/Docks/GenghisBarn.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Docks
+{
+ ///
+ /// Genghis Barn is a customer.
+ /// He lives in the Docks region.
+ /// Genghis is the NPC with a mohawk!
+ ///
+ public class GenghisBarn : NPC
+ {
+ internal GenghisBarn() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "genghis_barn")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Docks/JaneLucero.cs b/S1API/Entities/NPCs/Docks/JaneLucero.cs
new file mode 100644
index 00000000..3d041ac9
--- /dev/null
+++ b/S1API/Entities/NPCs/Docks/JaneLucero.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Docks
+{
+ ///
+ /// Jane Lucero is a dealer.
+ /// She lives in the Docks region.
+ /// Jane is the dealer with a tear tattoo!
+ ///
+ public class JaneLucero : NPC
+ {
+ internal JaneLucero() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "jane_lucero")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Docks/JavierPerez.cs b/S1API/Entities/NPCs/Docks/JavierPerez.cs
new file mode 100644
index 00000000..a5bd941e
--- /dev/null
+++ b/S1API/Entities/NPCs/Docks/JavierPerez.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Docks
+{
+ ///
+ /// Javier Perez is a customer.
+ /// He lives in the Docks region.
+ /// Javier works night shift at the Gas-Mart!
+ ///
+ public class JavierPerez : NPC
+ {
+ internal JavierPerez() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "javier_perez")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Docks/LisaGardener.cs b/S1API/Entities/NPCs/Docks/LisaGardener.cs
new file mode 100644
index 00000000..0e87ae8e
--- /dev/null
+++ b/S1API/Entities/NPCs/Docks/LisaGardener.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Docks
+{
+ ///
+ /// Lisa Gardener is a customer.
+ /// She lives in the Docks region.
+ /// Lisa is the NPC wearing blue scrubs!
+ ///
+ public class LisaGardener : NPC
+ {
+ internal LisaGardener() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "lisa_gardener")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Docks/MacCooper.cs b/S1API/Entities/NPCs/Docks/MacCooper.cs
new file mode 100644
index 00000000..ec01cc86
--- /dev/null
+++ b/S1API/Entities/NPCs/Docks/MacCooper.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Docks
+{
+ ///
+ /// Mac Cooper is a customer.
+ /// He lives in the Docks region.
+ /// Mac is the NPC with a blonde mohawk and gold shades!
+ ///
+ public class MacCooper : NPC
+ {
+ internal MacCooper() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "mac_cooper")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Docks/MarcoBaron.cs b/S1API/Entities/NPCs/Docks/MarcoBaron.cs
new file mode 100644
index 00000000..5c76f853
--- /dev/null
+++ b/S1API/Entities/NPCs/Docks/MarcoBaron.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Docks
+{
+ ///
+ /// Marco Baron is a customer.
+ /// He lives in the Docks region.
+ /// Marco is the NPC that runs the Auto Shop!
+ ///
+ public class MarcoBaron : NPC
+ {
+ internal MarcoBaron() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "marco_baron")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Docks/MelissaWood.cs b/S1API/Entities/NPCs/Docks/MelissaWood.cs
new file mode 100644
index 00000000..f60c8e1f
--- /dev/null
+++ b/S1API/Entities/NPCs/Docks/MelissaWood.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Docks
+{
+ ///
+ /// Melissa Wood is a customer.
+ /// She lives in the Docks region.
+ /// Melissa is the Blackjack dealer at the casino!
+ ///
+ public class MelissaWood : NPC
+ {
+ internal MelissaWood() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "melissa_wood")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Docks/SalvadorMoreno.cs b/S1API/Entities/NPCs/Docks/SalvadorMoreno.cs
new file mode 100644
index 00000000..52b28808
--- /dev/null
+++ b/S1API/Entities/NPCs/Docks/SalvadorMoreno.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Docks
+{
+ ///
+ /// Salvador Moreno is a supplier.
+ /// He lives in the Docks region.
+ /// Salvador is the NPC that supplies coca seeds to the player!
+ ///
+ public class SalvadorMoreno : NPC
+ {
+ internal SalvadorMoreno() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "salvador_moreno")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Downtown/BradCrosby.cs b/S1API/Entities/NPCs/Downtown/BradCrosby.cs
new file mode 100644
index 00000000..3e80ad39
--- /dev/null
+++ b/S1API/Entities/NPCs/Downtown/BradCrosby.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Downtown
+{
+ ///
+ /// Brad Crosby is a dealer.
+ /// He lives in the Downtown region.
+ /// Brad lives in a tent at the parking garage next to the casino!
+ ///
+ public class BradCrosby : NPC
+ {
+ internal BradCrosby() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "brad_crosby")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Downtown/ElizabethHomley.cs b/S1API/Entities/NPCs/Downtown/ElizabethHomley.cs
new file mode 100644
index 00000000..1f709e6a
--- /dev/null
+++ b/S1API/Entities/NPCs/Downtown/ElizabethHomley.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Downtown
+{
+ ///
+ /// Elizabeth Homley is a customer.
+ /// She lives in the Downtown region.
+ /// Elizabeth is the NPC is lightning blue hair!
+ ///
+ public class ElizabethHomley : NPC
+ {
+ internal ElizabethHomley() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "elizabeth_homley")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Downtown/EugeneBuckley.cs b/S1API/Entities/NPCs/Downtown/EugeneBuckley.cs
new file mode 100644
index 00000000..6cc2b80b
--- /dev/null
+++ b/S1API/Entities/NPCs/Downtown/EugeneBuckley.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Downtown
+{
+ ///
+ /// Eugene Buckley is a customer.
+ /// He lives in the Downtown region.
+ /// Eugene is the NPC with light brown hair, freckles, and black glasses!
+ ///
+ public class EugeneBuckley : NPC
+ {
+ internal EugeneBuckley() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "eugene_buckley")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Downtown/GregFliggle.cs b/S1API/Entities/NPCs/Downtown/GregFliggle.cs
new file mode 100644
index 00000000..f37072ee
--- /dev/null
+++ b/S1API/Entities/NPCs/Downtown/GregFliggle.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Downtown
+{
+ ///
+ /// Greg Fliggle is a customer.
+ /// He lives in the Downtown region.
+ /// Greg is the NPC with a teardrop tattoo and wrinkles!
+ ///
+ public class GregFliggle : NPC
+ {
+ internal GregFliggle() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "greg_fliggle")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Downtown/JeffGilmore.cs b/S1API/Entities/NPCs/Downtown/JeffGilmore.cs
new file mode 100644
index 00000000..38774f22
--- /dev/null
+++ b/S1API/Entities/NPCs/Downtown/JeffGilmore.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Downtown
+{
+ ///
+ /// Jeff Gilmore is a customer.
+ /// He lives in the Downtown region.
+ /// Jeff is the NPC that runs the skateboard shop!
+ ///
+ public class JeffGilmore : NPC
+ {
+ internal JeffGilmore() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "jeff_gilmore")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Downtown/JenniferRivera.cs b/S1API/Entities/NPCs/Downtown/JenniferRivera.cs
new file mode 100644
index 00000000..153638a4
--- /dev/null
+++ b/S1API/Entities/NPCs/Downtown/JenniferRivera.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Downtown
+{
+ ///
+ /// Jennifer Rivera is a customer.
+ /// She lives in the Downtown region.
+ /// Jennifer is the NPC with blonde haired buns!
+ ///
+ public class JenniferRivera : NPC
+ {
+ internal JenniferRivera() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "jennifer_rivera")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Downtown/KevinOakley.cs b/S1API/Entities/NPCs/Downtown/KevinOakley.cs
new file mode 100644
index 00000000..55d6f886
--- /dev/null
+++ b/S1API/Entities/NPCs/Downtown/KevinOakley.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Downtown
+{
+ ///
+ /// Kevin Oakley is a customer.
+ /// He lives in the Downtown region.
+ /// Kevin is the NPC wearing a green apron!
+ ///
+ public class KevinOakley : NPC
+ {
+ internal KevinOakley() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "kevin_oakley")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Downtown/LouisFourier.cs b/S1API/Entities/NPCs/Downtown/LouisFourier.cs
new file mode 100644
index 00000000..be7a9816
--- /dev/null
+++ b/S1API/Entities/NPCs/Downtown/LouisFourier.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Downtown
+{
+ ///
+ /// Louis Fourier is a customer.
+ /// He lives in the Downtown region.
+ /// Louis is the NPC with a chef's hat!
+ ///
+ public class LouisFourier : NPC
+ {
+ internal LouisFourier() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "louis_fourier")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Downtown/LucyPennington.cs b/S1API/Entities/NPCs/Downtown/LucyPennington.cs
new file mode 100644
index 00000000..91cdb9f7
--- /dev/null
+++ b/S1API/Entities/NPCs/Downtown/LucyPennington.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Downtown
+{
+ ///
+ /// Lucy Pennington is a customer.
+ /// She lives in the Downtown region.
+ /// Lucy is the NPC with blonde haired buns up high!
+ ///
+ public class LucyPennington : NPC
+ {
+ internal LucyPennington() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "lucy_pennington")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Downtown/PhilipWentworth.cs b/S1API/Entities/NPCs/Downtown/PhilipWentworth.cs
new file mode 100644
index 00000000..2c136602
--- /dev/null
+++ b/S1API/Entities/NPCs/Downtown/PhilipWentworth.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Downtown
+{
+ ///
+ /// Philip Wentworth is a customer.
+ /// He lives in the Downtown region.
+ /// Philip is the bald NPC with a goatee!
+ ///
+ public class PhilipWentworth : NPC
+ {
+ internal PhilipWentworth() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "philip_wentworth")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Downtown/RandyCaulfield.cs b/S1API/Entities/NPCs/Downtown/RandyCaulfield.cs
new file mode 100644
index 00000000..db3c1c19
--- /dev/null
+++ b/S1API/Entities/NPCs/Downtown/RandyCaulfield.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Downtown
+{
+ ///
+ /// Randy Caulfield is a customer.
+ /// He lives in the Downtown region.
+ /// Randy is the NPC wearing a green hat!
+ ///
+ public class RandyCaulfield : NPC
+ {
+ internal RandyCaulfield() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "randy_caulfield")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/IgorRomanovich.cs b/S1API/Entities/NPCs/IgorRomanovich.cs
new file mode 100644
index 00000000..8ee77af1
--- /dev/null
+++ b/S1API/Entities/NPCs/IgorRomanovich.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs
+{
+ ///
+ /// Igor Romanovich is a npc.
+ /// He is Manny's bodyguard.
+ /// Igor can be found inside the Warehouse!
+ ///
+ public class IgorRomanovich : NPC
+ {
+ internal IgorRomanovich() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "igor_romanovich")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/MannyOakfield.cs b/S1API/Entities/NPCs/MannyOakfield.cs
new file mode 100644
index 00000000..928e373b
--- /dev/null
+++ b/S1API/Entities/NPCs/MannyOakfield.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs
+{
+ ///
+ /// Manny is a NPC.
+ /// He provides workers to the player.
+ /// Manny can be found in the Warehouse!
+ ///
+ public class MannyOakfield : NPC
+ {
+ internal MannyOakfield() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "manny_oakfield")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/NPC_LIST.md b/S1API/Entities/NPCs/NPC_LIST.md
new file mode 100644
index 00000000..334576fa
--- /dev/null
+++ b/S1API/Entities/NPCs/NPC_LIST.md
@@ -0,0 +1,260 @@
+**manny_oakfield**
+ScheduleOne.NPCs.CharacterClasses.Fixer
+---
+**jessi_waters**
+ScheduleOne.NPCs.CharacterClasses.Jessi
+---
+**brad_crosby**
+ScheduleOne.NPCs.CharacterClasses.Brad
+---
+**donna_martin**
+ScheduleOne.NPCs.NPC
+---
+**keith_wagner**
+ScheduleOne.NPCs.CharacterClasses.Keith
+---
+**kevin_oakley**
+ScheduleOne.NPCs.CharacterClasses.Kevin
+---
+**billy_kramer**
+ScheduleOne.NPCs.Billy
+---
+**marco_baron**
+ScheduleOne.NPCs.CharacterClasses.Marco
+---
+**officergreen**
+ScheduleOne.Police.PoliceOfficer
+---
+**stan_carney**
+ScheduleOne.NPCs.Stan
+---
+**kathy_henderson**
+ScheduleOne.NPCs.CharacterClasses.Kathy
+---
+**jane_lucero**
+ScheduleOne.NPCs.CharacterClasses.Jane
+---
+**beth_penn**
+ScheduleOne.NPCs.CharacterClasses.Beth
+---
+**officerhoward**
+ScheduleOne.Police.PoliceOfficer
+---
+**officerlopez**
+ScheduleOne.Police.PoliceOfficer
+---
+**tobias_wentworth**
+ScheduleOne.NPCs.CharacterClasses.Tobias
+---
+**kim_delaney**
+ScheduleOne.NPCs.CharacterClasses.Kim
+---
+**officerdavis**
+ScheduleOne.Police.PoliceOfficer
+---
+**officermurphy**
+ScheduleOne.Police.PoliceOfficer
+---
+**lisa_gardener**
+ScheduleOne.NPCs.CharacterClasses.Lisa
+---
+**eugene_buckley**
+ScheduleOne.NPCs.CharacterClasses.Eugene
+---
+**karen_kennedy**
+ScheduleOne.NPCs.CharacterClasses.Karen
+---
+**jen_heard**
+ScheduleOne.NPCs.CharacterClasses.Jen
+---
+**michael_boog**
+ScheduleOne.NPCs.CharacterClasses.Michael
+---
+**dean_webster**
+ScheduleOne.NPCs.CharacterClasses.Dean
+---
+**elizabeth_homley**
+ScheduleOne.NPCs.CharacterClasses.Elizabeth
+---
+**herbert_bleuball**
+ScheduleOne.NPCs.CharacterClasses.Herbert
+---
+**mick_lubbin**
+ScheduleOne.NPCs.CharacterClasses.Mick
+---
+**mac_cooper**
+ScheduleOne.NPCs.CharacterClasses.Mac
+---
+**sam_thompson**
+ScheduleOne.NPCs.CharacterClasses.Sam
+---
+**molly_presley**
+ScheduleOne.Economy.Dealer
+---
+**greg_fliggle**
+ScheduleOne.NPCs.CharacterClasses.Greg
+---
+**jeff_gilmore**
+ScheduleOne.NPCs.CharacterClasses.Jeff
+---
+**benji_coleman**
+ScheduleOne.NPCs.CharacterClasses.Benji
+---
+**albert_hoover**
+ScheduleOne.NPCs.CharacterClasses.Albert
+---
+**chris_sullivan**
+ScheduleOne.NPCs.CharacterClasses.Chris
+---
+**oscar_holland**
+ScheduleOne.NPCs.CharacterClasses.Oscar
+---
+**peter_file**
+ScheduleOne.NPCs.CharacterClasses.Peter
+---
+**jennifer_rivera**
+ScheduleOne.NPCs.CharacterClasses.Jennifer
+---
+**hank_stevenson**
+ScheduleOne.NPCs.CharacterClasses.Steve
+---
+**officerjackson**
+ScheduleOne.Police.PoliceOfficer
+---
+**louis_fourier**
+ScheduleOne.NPCs.CharacterClasses.Louis
+---
+**officerlee**
+ScheduleOne.Police.PoliceOfficer
+---
+**melissa_wood**
+ScheduleOne.NPCs.CharacterClasses.Melissa
+---
+**jackie_stevenson**
+ScheduleOne.NPCs.CharacterClasses.Jackie
+---
+**lucy_pennington**
+ScheduleOne.NPCs.NPC
+---
+**peggy_myers**
+ScheduleOne.NPCs.CharacterClasses.Peggy
+---
+**joyce_ball**
+ScheduleOne.NPCs.NPC
+---
+**meg_cooley**
+ScheduleOne.NPCs.Meg
+---
+**trent_sherman**
+ScheduleOne.NPCs.NPC
+---
+**charles_rowland**
+ScheduleOne.NPCs.CharacterClasses.Charles
+---
+**george_greene**
+ScheduleOne.NPCs.CharacterClasses.George
+---
+**jerry_montero**
+ScheduleOne.NPCs.Jerry
+---
+**doris_lubbin**
+ScheduleOne.NPCs.Doris
+---
+**dan_samwell**
+ScheduleOne.NPCs.CharacterClasses.Dan
+---
+**uncle_nelson**
+ScheduleOne.NPCs.CharacterClasses.UncleNelson
+---
+**igor_romanovich**
+ScheduleOne.NPCs.CharacterClasses.Igor
+---
+**wei_long**
+ScheduleOne.NPCs.CharacterClasses.Wei
+---
+**leo_rivers**
+ScheduleOne.NPCs.CharacterClasses.Leo
+---
+**officerbailey**
+ScheduleOne.Police.PoliceOfficer
+---
+**officercooper**
+ScheduleOne.Police.PoliceOfficer
+---
+**officeroakley**
+ScheduleOne.Police.PoliceOfficer
+---
+**lily_turner**
+ScheduleOne.NPCs.CharacterClasses.Lily
+---
+**ray_hoffman**
+ScheduleOne.NPCs.CharacterClasses.Ray
+---
+**walter_cussler**
+ScheduleOne.NPCs.CharacterClasses.Walter
+---
+**pearl_moore**
+ScheduleOne.NPCs.CharacterClasses.Pearl
+---
+**fiona_hancock**
+ScheduleOne.NPCs.CharacterClasses.Fiona
+---
+**genghis_barn**
+ScheduleOne.NPCs.CharacterClasses.Genghis
+---
+**anna_chesterfield**
+ScheduleOne.NPCs.CharacterClasses.Anna
+---
+**javier_perez**
+ScheduleOne.NPCs.CharacterClasses.Javier
+---
+**cranky_frank**
+ScheduleOne.NPCs.CharacterClasses.Frank
+---
+**randy_caulfield**
+ScheduleOne.NPCs.CharacterClasses.Randy
+---
+**philip_wentworth**
+ScheduleOne.NPCs.CharacterClasses.Philip
+---
+**jeremy_wilkinson**
+ScheduleOne.NPCs.CharacterClasses.Jeremy
+---
+**alison_knight**
+ScheduleOne.NPCs.CharacterClasses.Alison
+---
+**carl_bundy**
+ScheduleOne.NPCs.CharacterClasses.Carl
+---
+**harold_colt**
+ScheduleOne.NPCs.CharacterClasses.Harold
+---
+**jack_knight**
+ScheduleOne.NPCs.CharacterClasses.Jack
+---
+**dennis_kennedy**
+ScheduleOne.NPCs.CharacterClasses.Dennis
+---
+**shirley_watts**
+ScheduleOne.NPCs.CharacterClasses.Shirley
+---
+**salvador_moreno**
+ScheduleOne.NPCs.CharacterClasses.Salvador
+---
+**kyle_cooley**
+ScheduleOne.NPCs.CharacterClasses.Kyle
+---
+**ludwig_meyer**
+ScheduleOne.NPCs.NPC
+---
+**austin_steiner**
+ScheduleOne.NPCs.CharacterClasses.Austin
+---
+**chloe_bowers**
+ScheduleOne.NPCs.Chloe
+---
+**ming**
+ScheduleOne.NPCs.CharacterClasses.Ming
+---
+**geraldine_poon**
+ScheduleOne.NPCs.CharacterClasses.Geraldine
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/AlbertHoover.cs b/S1API/Entities/NPCs/Northtown/AlbertHoover.cs
new file mode 100644
index 00000000..23a30f0b
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/AlbertHoover.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Albert Hoover is a supplier.
+ /// He lives in the Northtown region.
+ /// Albert is the supplier for weed seeds!
+ ///
+ public class AlbertHoover : NPC
+ {
+ internal AlbertHoover() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "albert_hoover")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/AustinSteiner.cs b/S1API/Entities/NPCs/Northtown/AustinSteiner.cs
new file mode 100644
index 00000000..2daa4e34
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/AustinSteiner.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Austin Steiner is a customer.
+ /// He lives in the Northtown region.
+ /// Austin is the NPC with a red/orange afro and black glasses!
+ ///
+ public class AustinSteiner : NPC
+ {
+ internal AustinSteiner() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "austin_steiner")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/BenjiColeman.cs b/S1API/Entities/NPCs/Northtown/BenjiColeman.cs
new file mode 100644
index 00000000..35b1deb6
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/BenjiColeman.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Benji Coleman is a dealer.
+ /// He lives in the Northtown region.
+ /// Benji lives at the motel in room #2.
+ /// He is the first dealer the player unlocks!
+ ///
+ public class BenjiColeman : NPC
+ {
+ internal BenjiColeman() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "benji_coleman")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/BethPenn.cs b/S1API/Entities/NPCs/Northtown/BethPenn.cs
new file mode 100644
index 00000000..31914721
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/BethPenn.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Beth Penn is a customer.
+ /// She lives in the Northtown region.
+ /// Beth is the NPC with a blonde bowl cut and wears green glasses!
+ ///
+ public class BethPenn : NPC
+ {
+ internal BethPenn() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "beth_penn")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/ChloeBowers.cs b/S1API/Entities/NPCs/Northtown/ChloeBowers.cs
new file mode 100644
index 00000000..a4989c5a
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/ChloeBowers.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Chloe Bowers is a customer.
+ /// She lives in the Northtown region.
+ /// Chloe is the NPC with long, straight, red hair!
+ ///
+ public class ChloeBowers : NPC
+ {
+ internal ChloeBowers() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "chloe_bowers")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/DonnaMartin.cs b/S1API/Entities/NPCs/Northtown/DonnaMartin.cs
new file mode 100644
index 00000000..b745d44f
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/DonnaMartin.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Donna Martin is a customer.
+ /// She lives in the Northtown region.
+ /// Donna is the attendant of the Motel!
+ ///
+ public class DonnaMartin : NPC
+ {
+ internal DonnaMartin() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "donna_martin")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/GeraldinePoon.cs b/S1API/Entities/NPCs/Northtown/GeraldinePoon.cs
new file mode 100644
index 00000000..9c24b6b4
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/GeraldinePoon.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Geraldine Poon is a customer.
+ /// He lives in the Northtown region.
+ /// Geraldine is the balding NPC with small gold glasses!
+ ///
+ public class GeraldinePoon : NPC
+ {
+ internal GeraldinePoon() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "geraldine_poon")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/JessiWaters.cs b/S1API/Entities/NPCs/Northtown/JessiWaters.cs
new file mode 100644
index 00000000..8b227ff1
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/JessiWaters.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Jessi Waters is a customer.
+ /// She lives in the Northtown region.
+ /// Jessi is the purple haired NPC with face tattoos!
+ ///
+ public class JessiWaters : NPC
+ {
+ internal JessiWaters() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "jessi_waters")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/KathyHenderson.cs b/S1API/Entities/NPCs/Northtown/KathyHenderson.cs
new file mode 100644
index 00000000..4ac0dcfe
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/KathyHenderson.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Kathy Henderson is a customer.
+ /// She lives in the Northtown region.
+ /// Kathy is the NPC with long blonde hair with bangs!
+ ///
+ public class KathyHenderson : NPC
+ {
+ internal KathyHenderson() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "kathy_henderson")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/KyleCooley.cs b/S1API/Entities/NPCs/Northtown/KyleCooley.cs
new file mode 100644
index 00000000..523dcab7
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/KyleCooley.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Kyle Cooley is a customer.
+ /// He lives in the Northtown region.
+ /// Kyle is the NPC that works at Taco Ticklers!
+ ///
+ public class KyleCooley : NPC
+ {
+ internal KyleCooley() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "kyle_cooley")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/LudwigMeyer.cs b/S1API/Entities/NPCs/Northtown/LudwigMeyer.cs
new file mode 100644
index 00000000..21653c19
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/LudwigMeyer.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Ludwig Meyer is a customer.
+ /// He lives in the Northtown region.
+ /// Ludwig is the NPC with spiky hair and gold glasses!
+ ///
+ public class LudwigMeyer : NPC
+ {
+ internal LudwigMeyer() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "ludwig_meyer")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/MickLubbin.cs b/S1API/Entities/NPCs/Northtown/MickLubbin.cs
new file mode 100644
index 00000000..8ab5f9b6
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/MickLubbin.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Mick Lubbin is a customer.
+ /// He lives in the Northtown region.
+ /// Mick is the owner of the pawn shop!
+ ///
+ public class MickLubbin : NPC
+ {
+ internal MickLubbin() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "mick_lubbin")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/Ming.cs b/S1API/Entities/NPCs/Northtown/Ming.cs
new file mode 100644
index 00000000..ea1e1721
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/Ming.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Mrs. Ming is a customer.
+ /// She lives in the Northtown region.
+ /// Ming is the NPC that owns the chinese restaurant!
+ ///
+ public class Ming : NPC
+ {
+ internal Ming() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "ming")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/PeggyMyers.cs b/S1API/Entities/NPCs/Northtown/PeggyMyers.cs
new file mode 100644
index 00000000..ae7660a3
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/PeggyMyers.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Peggy Myers is a customer.
+ /// She lives in the Northtown region.
+ /// Peggy is the NPC with freckles and brown hair pulled back!
+ ///
+ public class PeggyMyers : NPC
+ {
+ internal PeggyMyers() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "peggy_myers")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/PeterFile.cs b/S1API/Entities/NPCs/Northtown/PeterFile.cs
new file mode 100644
index 00000000..6ae508d7
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/PeterFile.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Peter File is a customer.
+ /// He lives in the Northtown region.
+ /// Peter is the NPC with a black bowl cut and black glasses!
+ ///
+ public class PeterFile : NPC
+ {
+ internal PeterFile() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "peter_file")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Northtown/SamThompson.cs b/S1API/Entities/NPCs/Northtown/SamThompson.cs
new file mode 100644
index 00000000..4f349c23
--- /dev/null
+++ b/S1API/Entities/NPCs/Northtown/SamThompson.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Northtown
+{
+ ///
+ /// Sam Thompson is a customer.
+ /// He lives in the Northtown region.
+ /// Sam is the NPC with a green hair and wrinkles!
+ ///
+ public class SamThompson : NPC
+ {
+ internal SamThompson() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "sam_thompson")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/OscarHolland.cs b/S1API/Entities/NPCs/OscarHolland.cs
new file mode 100644
index 00000000..3349bf8b
--- /dev/null
+++ b/S1API/Entities/NPCs/OscarHolland.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs
+{
+ ///
+ /// Oscar Holland is a NPC.
+ /// He is a supplier located in the Warehouse!
+ ///
+ public class OscarHolland : NPC
+ {
+ internal OscarHolland() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "oscar_holland")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/PoliceOfficers/OfficerBailey.cs b/S1API/Entities/NPCs/PoliceOfficers/OfficerBailey.cs
new file mode 100644
index 00000000..46fb882f
--- /dev/null
+++ b/S1API/Entities/NPCs/PoliceOfficers/OfficerBailey.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.PoliceOfficers
+{
+ ///
+ /// Officer Bailey is a police officer.
+ /// He is the bald officer with a swirling mustache!
+ ///
+ public class OfficerBailey : NPC
+ {
+ internal OfficerBailey() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "officerbailey")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/PoliceOfficers/OfficerCooper.cs b/S1API/Entities/NPCs/PoliceOfficers/OfficerCooper.cs
new file mode 100644
index 00000000..64555f6a
--- /dev/null
+++ b/S1API/Entities/NPCs/PoliceOfficers/OfficerCooper.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.PoliceOfficers
+{
+ ///
+ /// Officer Cooper is a police officer.
+ /// She is the officer with two high black buns and black glasses!
+ ///
+ public class OfficerCooper : NPC
+ {
+ internal OfficerCooper() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "officercooper")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/PoliceOfficers/OfficerGreen.cs b/S1API/Entities/NPCs/PoliceOfficers/OfficerGreen.cs
new file mode 100644
index 00000000..1757e3c2
--- /dev/null
+++ b/S1API/Entities/NPCs/PoliceOfficers/OfficerGreen.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.PoliceOfficers
+{
+ ///
+ /// Officer Green is a police officer.
+ /// She is the officer with light brown hair in a bun!
+ ///
+ public class OfficerGreen : NPC
+ {
+ internal OfficerGreen() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "officergreen")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/PoliceOfficers/OfficerHoward.cs b/S1API/Entities/NPCs/PoliceOfficers/OfficerHoward.cs
new file mode 100644
index 00000000..a2866ec7
--- /dev/null
+++ b/S1API/Entities/NPCs/PoliceOfficers/OfficerHoward.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.PoliceOfficers
+{
+ ///
+ /// Officer Howard is a police officer.
+ /// He is the officer with a light brown afro and goatee!
+ ///
+ public class OfficerHoward : NPC
+ {
+ internal OfficerHoward() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "officerhoward")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/PoliceOfficers/OfficerJackson.cs b/S1API/Entities/NPCs/PoliceOfficers/OfficerJackson.cs
new file mode 100644
index 00000000..f07628da
--- /dev/null
+++ b/S1API/Entities/NPCs/PoliceOfficers/OfficerJackson.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.PoliceOfficers
+{
+ ///
+ /// Officer Jackson is a police officer.
+ /// He is the officer with a light brown goatee and police hat!
+ ///
+ public class OfficerJackson : NPC
+ {
+ internal OfficerJackson() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "officerjackson")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/PoliceOfficers/OfficerLee.cs b/S1API/Entities/NPCs/PoliceOfficers/OfficerLee.cs
new file mode 100644
index 00000000..4048acab
--- /dev/null
+++ b/S1API/Entities/NPCs/PoliceOfficers/OfficerLee.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.PoliceOfficers
+{
+ ///
+ /// Officer Lee is a police officer.
+ /// He is the officer with a button-up shirt and black hair!
+ ///
+ public class OfficerLee : NPC
+ {
+ internal OfficerLee() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "officerlee")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/PoliceOfficers/OfficerLopez.cs b/S1API/Entities/NPCs/PoliceOfficers/OfficerLopez.cs
new file mode 100644
index 00000000..f98ec696
--- /dev/null
+++ b/S1API/Entities/NPCs/PoliceOfficers/OfficerLopez.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.PoliceOfficers
+{
+ ///
+ /// Officer Lopez is a police officer.
+ /// She is the officer with a blue button-up and long black hair!
+ ///
+ public class OfficerLopez : NPC
+ {
+ internal OfficerLopez() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "officerlopez")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/PoliceOfficers/OfficerMurphy.cs b/S1API/Entities/NPCs/PoliceOfficers/OfficerMurphy.cs
new file mode 100644
index 00000000..69cf7610
--- /dev/null
+++ b/S1API/Entities/NPCs/PoliceOfficers/OfficerMurphy.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.PoliceOfficers
+{
+ ///
+ /// Officer Murphy is a police officer.
+ /// He is the balding officer with grey hair and wrinkles!
+ ///
+ public class OfficerMurphy : NPC
+ {
+ internal OfficerMurphy() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "officermurphy")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/PoliceOfficers/OfficerOakley.cs b/S1API/Entities/NPCs/PoliceOfficers/OfficerOakley.cs
new file mode 100644
index 00000000..61a2159a
--- /dev/null
+++ b/S1API/Entities/NPCs/PoliceOfficers/OfficerOakley.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.PoliceOfficers
+{
+ ///
+ /// Officer Oakley is a police officer.
+ /// He is the officer with light brown spiky hair and a goatee!
+ ///
+ public class OfficerOakley : NPC
+ {
+ internal OfficerOakley() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "officeroakley")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/StanCarney.cs b/S1API/Entities/NPCs/StanCarney.cs
new file mode 100644
index 00000000..196975e1
--- /dev/null
+++ b/S1API/Entities/NPCs/StanCarney.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs
+{
+ ///
+ /// Stan Carney is a NPC.
+ /// He is the NPC that sells weapons.
+ /// Stan can be found in the Warehouse!
+ ///
+ public class StanCarney : NPC
+ {
+ internal StanCarney() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "stan_carney")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Suburbia/AlisonKnight.cs b/S1API/Entities/NPCs/Suburbia/AlisonKnight.cs
new file mode 100644
index 00000000..b8023319
--- /dev/null
+++ b/S1API/Entities/NPCs/Suburbia/AlisonKnight.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Suburbia
+{
+ ///
+ /// Alison Knight is a customer.
+ /// She lives in the Suburbia region.
+ /// Alison is the NPC with long light brown hair!
+ ///
+ public class AlisonKnight : NPC
+ {
+ internal AlisonKnight() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "alison_knight")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Suburbia/CarlBundy.cs b/S1API/Entities/NPCs/Suburbia/CarlBundy.cs
new file mode 100644
index 00000000..663e1a59
--- /dev/null
+++ b/S1API/Entities/NPCs/Suburbia/CarlBundy.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Suburbia
+{
+ ///
+ /// Carl Bundy is a customer.
+ /// He lives in the Suburbia region.
+ /// Carl is the NPC with a brown apron!
+ ///
+ public class CarlBundy : NPC
+ {
+ internal CarlBundy() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "carl_bundy")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Suburbia/ChrisSullivan.cs b/S1API/Entities/NPCs/Suburbia/ChrisSullivan.cs
new file mode 100644
index 00000000..0360fc4f
--- /dev/null
+++ b/S1API/Entities/NPCs/Suburbia/ChrisSullivan.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Suburbia
+{
+ ///
+ /// Chris Sullivan is a customer.
+ /// He lives in the Suburbia region.
+ /// Chris is the NPC with black spiky hair and black glasses!
+ ///
+ public class ChrisSullivan : NPC
+ {
+ internal ChrisSullivan() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "chris_sullivan")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Suburbia/DennisKennedy.cs b/S1API/Entities/NPCs/Suburbia/DennisKennedy.cs
new file mode 100644
index 00000000..df287efb
--- /dev/null
+++ b/S1API/Entities/NPCs/Suburbia/DennisKennedy.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Suburbia
+{
+ ///
+ /// Dennis Kennedy is a customer.
+ /// He lives in the Suburbia region.
+ /// Dennis is the NPC with light blonde spiky hair and a thick mustache!
+ ///
+ public class DennisKennedy : NPC
+ {
+ internal DennisKennedy() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "dennis_kennedy")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Suburbia/HankStevenson.cs b/S1API/Entities/NPCs/Suburbia/HankStevenson.cs
new file mode 100644
index 00000000..ea0bf972
--- /dev/null
+++ b/S1API/Entities/NPCs/Suburbia/HankStevenson.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Suburbia
+{
+ ///
+ /// Hank Stevenson is a customer.
+ /// He lives in the Suburbia region.
+ /// Hank is the balding NPC with greying brown hair and a goatee!
+ ///
+ public class HankStevenson : NPC
+ {
+ internal HankStevenson() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "hank_stevenson")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Suburbia/HaroldColt.cs b/S1API/Entities/NPCs/Suburbia/HaroldColt.cs
new file mode 100644
index 00000000..e7442eb4
--- /dev/null
+++ b/S1API/Entities/NPCs/Suburbia/HaroldColt.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Suburbia
+{
+ ///
+ /// Harold Colt is a customer.
+ /// He lives in the Suburbia region.
+ /// Harold is the NPC with grey spiky hair and wrinkles!
+ ///
+ public class HaroldColt : NPC
+ {
+ internal HaroldColt() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "harold_colt")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Suburbia/JackKnight.cs b/S1API/Entities/NPCs/Suburbia/JackKnight.cs
new file mode 100644
index 00000000..36dbac8a
--- /dev/null
+++ b/S1API/Entities/NPCs/Suburbia/JackKnight.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Suburbia
+{
+ ///
+ /// Jack Knight is a customer.
+ /// He lives in the Suburbia region.
+ /// Jack is the balding NPC with small gold glasses!
+ ///
+ public class JackKnight : NPC
+ {
+ internal JackKnight() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "jack_knight")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Suburbia/JackieStevenson.cs b/S1API/Entities/NPCs/Suburbia/JackieStevenson.cs
new file mode 100644
index 00000000..76cee8c9
--- /dev/null
+++ b/S1API/Entities/NPCs/Suburbia/JackieStevenson.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Suburbia
+{
+ ///
+ /// Jackie Stevenson is a customer.
+ /// He lives in the Suburbia region.
+ /// Jackie is the NPC with short brown hair and light freckles!
+ ///
+ public class JackieStevenson : NPC
+ {
+ internal JackieStevenson() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "jackie_stevenson")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Suburbia/JeremyWilkinson.cs b/S1API/Entities/NPCs/Suburbia/JeremyWilkinson.cs
new file mode 100644
index 00000000..3ff5a30f
--- /dev/null
+++ b/S1API/Entities/NPCs/Suburbia/JeremyWilkinson.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Suburbia
+{
+ ///
+ /// Jeremy Wilkinson is a customer.
+ /// He lives in the Suburbia region.
+ /// Jeremy is the NPC that works at Hyland Auto!
+ ///
+ public class JeremyWilkinson : NPC
+ {
+ internal JeremyWilkinson() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "jeremy_wilkinson")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Suburbia/KarenKennedy.cs b/S1API/Entities/NPCs/Suburbia/KarenKennedy.cs
new file mode 100644
index 00000000..c6a0d6d9
--- /dev/null
+++ b/S1API/Entities/NPCs/Suburbia/KarenKennedy.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Suburbia
+{
+ ///
+ /// Karen Kennedy is a customer.
+ /// She lives in the Suburbia region.
+ /// Karen is the NPC with wavy blonde hair and purple eyelids!
+ /// She can be found at the casino upstairs when it's open.
+ ///
+ public class KarenKennedy : NPC
+ {
+ internal KarenKennedy() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "karen_kennedy")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Suburbia/WeiLong.cs b/S1API/Entities/NPCs/Suburbia/WeiLong.cs
new file mode 100644
index 00000000..2e82f1e9
--- /dev/null
+++ b/S1API/Entities/NPCs/Suburbia/WeiLong.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Suburbia
+{
+ ///
+ /// Wei Long is a dealer.
+ /// He lives in the Suburbia region.
+ /// Wei is the dealer with a black bowl cut and gold glasses!
+ ///
+ public class WeiLong : NPC
+ {
+ internal WeiLong() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "wei_long")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/UncleNelson.cs b/S1API/Entities/NPCs/UncleNelson.cs
new file mode 100644
index 00000000..99cd30e5
--- /dev/null
+++ b/S1API/Entities/NPCs/UncleNelson.cs
@@ -0,0 +1,18 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs
+{
+ ///
+ /// Uncle Nelson is a NPC.
+ /// He is the uncle of the main character!
+ ///
+ public class UncleNelson : NPC
+ {
+ internal UncleNelson() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "uncle_nelson")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Uptown/FionaHancock.cs b/S1API/Entities/NPCs/Uptown/FionaHancock.cs
new file mode 100644
index 00000000..0829fdb9
--- /dev/null
+++ b/S1API/Entities/NPCs/Uptown/FionaHancock.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+using NPC = S1API.Entities.NPC;
+
+namespace S1API.Entities.NPCs.Uptown
+{
+ ///
+ /// Fiona Hancock is a customer.
+ /// She lives in the Uptown region.
+ /// Fiona is the NPC with light brown buns and green glasses!
+ ///
+ public class FionaHancock : NPC
+ {
+ internal FionaHancock() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "fiona_hancock")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Uptown/HerbertBleuball.cs b/S1API/Entities/NPCs/Uptown/HerbertBleuball.cs
new file mode 100644
index 00000000..9c34e326
--- /dev/null
+++ b/S1API/Entities/NPCs/Uptown/HerbertBleuball.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+using NPC = S1API.Entities.NPC;
+
+namespace S1API.Entities.NPCs.Uptown
+{
+ ///
+ /// Herbert Bleuball is a customer.
+ /// He lives in the Uptown region.
+ /// Herbert is the NPC that owns Bleuball's Boutique!
+ ///
+ public class HerbertBleuball : NPC
+ {
+ internal HerbertBleuball() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "herbert_bleuball")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Uptown/JenHeard.cs b/S1API/Entities/NPCs/Uptown/JenHeard.cs
new file mode 100644
index 00000000..9cf9ea11
--- /dev/null
+++ b/S1API/Entities/NPCs/Uptown/JenHeard.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+using NPC = S1API.Entities.NPC;
+
+namespace S1API.Entities.NPCs.Uptown
+{
+ ///
+ /// Jen Heard is a customer.
+ /// She lives in the Uptown region.
+ /// Jen is the NPC with low orange buns!
+ ///
+ public class JenHeard : NPC
+ {
+ internal JenHeard() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "jen_heard")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Uptown/LeoRivers.cs b/S1API/Entities/NPCs/Uptown/LeoRivers.cs
new file mode 100644
index 00000000..d5fd88ea
--- /dev/null
+++ b/S1API/Entities/NPCs/Uptown/LeoRivers.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+using NPC = S1API.Entities.NPC;
+
+namespace S1API.Entities.NPCs.Uptown
+{
+ ///
+ /// Leo Rivers is a dealer.
+ /// He lives in the Uptown region.
+ /// Leo is the dealer wearing a black hat and gold shades!
+ ///
+ public class LeoRivers : NPC
+ {
+ internal LeoRivers() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "leo_rivers")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Uptown/LilyTurner.cs b/S1API/Entities/NPCs/Uptown/LilyTurner.cs
new file mode 100644
index 00000000..3a26941e
--- /dev/null
+++ b/S1API/Entities/NPCs/Uptown/LilyTurner.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+using NPC = S1API.Entities.NPC;
+
+namespace S1API.Entities.NPCs.Uptown
+{
+ ///
+ /// Lily Turner is a customer.
+ /// She lives in the Uptown region.
+ /// Lily is the NPC with long brown hair with bangs!
+ ///
+ public class LilyTurner : NPC
+ {
+ internal LilyTurner() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "lily_turner")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Uptown/MichaelBoog.cs b/S1API/Entities/NPCs/Uptown/MichaelBoog.cs
new file mode 100644
index 00000000..20798227
--- /dev/null
+++ b/S1API/Entities/NPCs/Uptown/MichaelBoog.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+using NPC = S1API.Entities.NPC;
+
+namespace S1API.Entities.NPCs.Uptown
+{
+ ///
+ /// Michael Boog is a customer.
+ /// He lives in the Uptown region.
+ /// Michael is the NPC with a bright blue flat cap and black glasses!
+ ///
+ public class MichaelBoog : NPC
+ {
+ internal MichaelBoog() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "michael_boog")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Uptown/PearlMoore.cs b/S1API/Entities/NPCs/Uptown/PearlMoore.cs
new file mode 100644
index 00000000..85075c02
--- /dev/null
+++ b/S1API/Entities/NPCs/Uptown/PearlMoore.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+using NPC = S1API.Entities.NPC;
+
+namespace S1API.Entities.NPCs.Uptown
+{
+ ///
+ /// Pearl Moore is a customer.
+ /// She lives in the Uptown region.
+ /// Pearl is the NPC with long white hair with bangs!
+ ///
+ public class PearlMoore : NPC
+ {
+ internal PearlMoore() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "pearl_moore")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Uptown/RayHoffman.cs b/S1API/Entities/NPCs/Uptown/RayHoffman.cs
new file mode 100644
index 00000000..3c5c6fc0
--- /dev/null
+++ b/S1API/Entities/NPCs/Uptown/RayHoffman.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+using NPC = S1API.Entities.NPC;
+
+namespace S1API.Entities.NPCs.Uptown
+{
+ ///
+ /// Ray Hoffman is a customer.
+ /// He lives in the Uptown region.
+ /// Ray is the NPC that owns Ray's Realty!
+ ///
+ public class RayHoffman : NPC
+ {
+ internal RayHoffman() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "ray_hoffman")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Uptown/TobiasWentworth.cs b/S1API/Entities/NPCs/Uptown/TobiasWentworth.cs
new file mode 100644
index 00000000..91064578
--- /dev/null
+++ b/S1API/Entities/NPCs/Uptown/TobiasWentworth.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+using NPC = S1API.Entities.NPC;
+
+namespace S1API.Entities.NPCs.Uptown
+{
+ ///
+ /// Tobias Wentworth is a customer.
+ /// He lives in the Uptown region.
+ /// Tobias is the balding NPC with extremely light brown hair and small black glasses!
+ ///
+ public class TobiasWentworth : NPC
+ {
+ internal TobiasWentworth() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "tobias_wentworth")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Uptown/WalterCussler.cs b/S1API/Entities/NPCs/Uptown/WalterCussler.cs
new file mode 100644
index 00000000..b870e8b7
--- /dev/null
+++ b/S1API/Entities/NPCs/Uptown/WalterCussler.cs
@@ -0,0 +1,20 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+using NPC = S1API.Entities.NPC;
+
+namespace S1API.Entities.NPCs.Uptown
+{
+ ///
+ /// Walter Cussler is a customer.
+ /// He lives in the Uptown region.
+ /// Walter is the NPC with white hair and dressed as a priest!
+ ///
+ public class WalterCussler : NPC
+ {
+ internal WalterCussler() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "walter_cussler")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/CharlesRowland.cs b/S1API/Entities/NPCs/Westville/CharlesRowland.cs
new file mode 100644
index 00000000..4c78605c
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/CharlesRowland.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// Charles Rowland is a customer.
+ /// He lives in the Westville region.
+ /// Charles is the bald NPC with black glasses!
+ ///
+ public class CharlesRowland : NPC
+ {
+ internal CharlesRowland() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "charles_rowland")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/DeanWebster.cs b/S1API/Entities/NPCs/Westville/DeanWebster.cs
new file mode 100644
index 00000000..381083bf
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/DeanWebster.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// Dean Webster is a customer.
+ /// He lives in the Westville region.
+ /// Dean is the NPC that owns Top Tattoo!
+ ///
+ public class DeanWebster : NPC
+ {
+ internal DeanWebster() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "dean_webster")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/DorisLubbin.cs b/S1API/Entities/NPCs/Westville/DorisLubbin.cs
new file mode 100644
index 00000000..03ea2f64
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/DorisLubbin.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// Doris Lubbin is a customer.
+ /// She lives in the Westville region.
+ /// Doris is the NPC with light brown, wavy hair and black glasses!
+ ///
+ public class DorisLubbin : NPC
+ {
+ internal DorisLubbin() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "doris_lubbin")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/GeorgeGreene.cs b/S1API/Entities/NPCs/Westville/GeorgeGreene.cs
new file mode 100644
index 00000000..31cebf2f
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/GeorgeGreene.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// George Greene is a customer.
+ /// He lives in the Westville region.
+ /// George is the NPC with light brown, spiky hair and gold glasses!
+ ///
+ public class GeorgeGreene : NPC
+ {
+ internal GeorgeGreene() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "george_greene")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/JerryMontero.cs b/S1API/Entities/NPCs/Westville/JerryMontero.cs
new file mode 100644
index 00000000..dd1ec080
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/JerryMontero.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// Jerry Montero is a customer.
+ /// He lives in the Westville region.
+ /// Jerry is the NPC with a green hat and black glasses!
+ ///
+ public class JerryMontero : NPC
+ {
+ internal JerryMontero() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "jerry_montero")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/JoyceBall.cs b/S1API/Entities/NPCs/Westville/JoyceBall.cs
new file mode 100644
index 00000000..c8b67a71
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/JoyceBall.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// Joyce Ball is a customer.
+ /// She lives in the Westville region.
+ /// Joyce is the NPC with light brown hair and wrinkles!
+ ///
+ public class JoyceBall : NPC
+ {
+ internal JoyceBall() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "joyce_ball")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/KeithWagner.cs b/S1API/Entities/NPCs/Westville/KeithWagner.cs
new file mode 100644
index 00000000..31c2a76e
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/KeithWagner.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// Keith Wagner is a customer.
+ /// He lives in the Westville region.
+ /// Keith is the NPC with blonde spiky hair and always angry!
+ ///
+ public class KeithWagner : NPC
+ {
+ internal KeithWagner() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "keith_wagner")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/KimDelaney.cs b/S1API/Entities/NPCs/Westville/KimDelaney.cs
new file mode 100644
index 00000000..91d54980
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/KimDelaney.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// Kim Delaney is a customer.
+ /// She lives in the Westville region.
+ /// Kim is the NPC with long, black hair with bangs!
+ ///
+ public class KimDelaney : NPC
+ {
+ internal KimDelaney() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "kim_delaney")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/MegCooley.cs b/S1API/Entities/NPCs/Westville/MegCooley.cs
new file mode 100644
index 00000000..d3123ec1
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/MegCooley.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// Meg Cooley is a customer.
+ /// She lives in the Westville region.
+ /// Meg is the npc with a mustard yellow bowl cut hairstyle!
+ ///
+ public class MegCooley : NPC
+ {
+ internal MegCooley() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "meg_cooley")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/MollyPresley.cs b/S1API/Entities/NPCs/Westville/MollyPresley.cs
new file mode 100644
index 00000000..82c26c84
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/MollyPresley.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// Molly Presley is a dealer.
+ /// She lives in the Westville region.
+ /// Molly is the dealer with gold shades and a red backward cap!
+ ///
+ public class MollyPresley : NPC
+ {
+ internal MollyPresley() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "molly_presley")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/ShirleyWatts.cs b/S1API/Entities/NPCs/Westville/ShirleyWatts.cs
new file mode 100644
index 00000000..16f044da
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/ShirleyWatts.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// Shirley Watts is a supplier.
+ /// She lives in the Westville region.
+ /// Shirley is the supplier for pseudo!
+ ///
+ public class ShirleyWatts : NPC
+ {
+ internal ShirleyWatts() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "shirley_watts")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/NPCs/Westville/TrentSherman.cs b/S1API/Entities/NPCs/Westville/TrentSherman.cs
new file mode 100644
index 00000000..5536466e
--- /dev/null
+++ b/S1API/Entities/NPCs/Westville/TrentSherman.cs
@@ -0,0 +1,19 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.NPCs;
+#else
+using ScheduleOne.NPCs;
+#endif
+using System.Linq;
+
+namespace S1API.Entities.NPCs.Westville
+{
+ ///
+ /// Trent Sherman is a customer.
+ /// He lives in the Westville region.
+ /// Trent is the NPC with short black hair and dark-colored skin!
+ ///
+ public class TrentSherman : NPC
+ {
+ internal TrentSherman() : base(NPCManager.NPCRegistry.ToArray().First(n => n.ID == "trent_sherman")) { }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Entities/Player.cs b/S1API/Entities/Player.cs
new file mode 100644
index 00000000..835bcf82
--- /dev/null
+++ b/S1API/Entities/Player.cs
@@ -0,0 +1,191 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using S1PlayerScripts = Il2CppScheduleOne.PlayerScripts;
+using S1Health = Il2CppScheduleOne.PlayerScripts.Health;
+#else
+using S1PlayerScripts = ScheduleOne.PlayerScripts;
+using S1Health = ScheduleOne.PlayerScripts.Health;
+#endif
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using HarmonyLib;
+using S1API.Entities.Interfaces;
+using S1API.Internal.Abstraction;
+using UnityEngine;
+
+namespace S1API.Entities
+{
+ ///
+ /// Represents a player within the game.
+ ///
+ public class Player : IEntity, IHealth
+ {
+ ///
+ /// Health when the player is invincible.
+ /// Invincibility isn't baked in the base game.
+ /// Hence, why we're doing it this way :).
+ ///
+ private const float InvincibleHealth = 1000000000f;
+
+ ///
+ /// The standard MAX_HEALTH of a player.
+ ///
+ private const float MortalHealth = 100f;
+
+ ///
+ /// All players currently in the game.
+ ///
+ public static readonly List All = new List();
+
+ ///
+ /// INTERNAL: Tracking of the S1 instance of the player.
+ ///
+ internal S1PlayerScripts.Player S1Player;
+
+ ///
+ /// INTERNAL: Constructor to create a new player from an S1 instance.
+ ///
+ ///
+ internal Player(S1PlayerScripts.Player player)
+ {
+ S1Player = player;
+ All.Add(this);
+ }
+
+ ///
+ /// The current client player (player executing your code).
+ ///
+ public static Player Local =>
+ All.FirstOrDefault(player => player.IsLocal)!;
+
+ ///
+ /// Whether this player is the client player or a networked player.
+ ///
+ public bool IsLocal =>
+ S1Player.IsLocalPlayer;
+
+ ///
+ /// The name of the player.
+ /// For single player, this appears to always return `Player`.
+ ///
+ public string Name =>
+ S1Player.PlayerName;
+
+ ///
+ /// INTERNAL: The game object associated with this player.
+ ///
+ GameObject IEntity.gameObject =>
+ S1Player.gameObject;
+
+ ///
+ /// The world position of the player.
+ ///
+ public Vector3 Position
+ {
+ get => ((IEntity)this).gameObject.transform.position;
+ set => ((IEntity)this).gameObject.transform.position = value;
+ }
+
+ ///
+ /// The transform of the player.
+ /// Please do not set the properties of the Transform.
+ ///
+ public Transform Transform =>
+ ((IEntity)this).gameObject.transform;
+
+ ///
+ /// The scale of the player.
+ ///
+ public float Scale
+ {
+ get => S1Player.Scale;
+ set => S1Player.SetScale(value);
+ }
+
+ ///
+ /// The current health of the player.
+ ///
+ public float CurrentHealth =>
+ S1Player.Health.CurrentHealth;
+
+ ///
+ /// The maximum health of the player.
+ ///
+ public float MaxHealth
+ {
+ get => (float)_maxHealthField.GetValue(S1Player.Health)!;
+ set => _maxHealthField.SetValue(S1Player.Health, value);
+ }
+
+ ///
+ /// Whether the player is dead or not.
+ ///
+ public bool IsDead =>
+ !S1Player.Health.IsAlive;
+
+ ///
+ /// Whether the player is invincible or not.
+ ///
+ public bool IsInvincible
+ {
+ get => MaxHealth == InvincibleHealth;
+ set
+ {
+ MaxHealth = value ? InvincibleHealth : MortalHealth;
+ S1Player.Health.SetHealth(MaxHealth);
+ }
+ }
+
+ ///
+ /// Revives the player.
+ ///
+ public void Revive() =>
+ S1Player.Health.Revive(Position, Quaternion.identity);
+
+ ///
+ /// Deals damage to the player.
+ ///
+ /// The amount of damage to deal.
+ public void Damage(int amount)
+ {
+ if (amount <= 0)
+ return;
+
+ S1Player.Health.TakeDamage(amount);
+ }
+
+ ///
+ /// Heals the player.
+ ///
+ /// The amount of healing to apply to the player.
+ public void Heal(int amount)
+ {
+ if (amount <= 0)
+ return;
+
+ S1Player.Health.SetHealth(CurrentHealth + amount);
+ }
+
+ ///
+ /// Kills the player.
+ ///
+ public void Kill() =>
+ S1Player.Health.SetHealth(0f);
+
+ ///
+ /// Called when the player dies.
+ ///
+ public event Action OnDeath
+ {
+ add => EventHelper.AddListener(value, S1Player.Health.onDie);
+ remove => EventHelper.RemoveListener(value, S1Player.Health.onDie);
+ }
+
+ ///
+ /// INTERNAL: Field access for the MAX_HEALTH const.
+ ///
+ private readonly FieldInfo _maxHealthField = AccessTools.Field(typeof(S1Health.PlayerHealth), "MAX_HEALTH");
+ }
+}
diff --git a/S1API/GameTime/Day.cs b/S1API/GameTime/Day.cs
index 61311920..38b2fa45 100644
--- a/S1API/GameTime/Day.cs
+++ b/S1API/GameTime/Day.cs
@@ -5,12 +5,39 @@
///
public enum Day
{
+ ///
+ /// Represents the first day of the week.
+ ///
Monday,
+
+ ///
+ /// Represents the second day of the week.
+ ///
Tuesday,
+
+ ///
+ /// Represents the third day of the week.
+ ///
Wednesday,
+
+ ///
+ /// Represents the fourth day of the week.
+ ///
Thursday,
+
+ ///
+ /// Represents the fifth day of the week.
+ ///
Friday,
+
+ ///
+ /// Represents the sixth day of the week.
+ ///
Saturday,
+
+ ///
+ /// Represents the seventh day of the week.
+ ///
Sunday
}
}
\ No newline at end of file
diff --git a/S1API/GameTime/GameDateTime.cs b/S1API/GameTime/GameDateTime.cs
index bf72ea81..643ce06e 100644
--- a/S1API/GameTime/GameDateTime.cs
+++ b/S1API/GameTime/GameDateTime.cs
@@ -1,22 +1,150 @@
-// TODO: Implement GameDateTime wrapper
-// #if (IL2CPP)
-// using S1GameTime = Il2CppScheduleOne.GameTime;
-// #elif (MONO)
-// using S1GameTime = ScheduleOne.GameTime;
-// #endif
-//
-// namespace S1API.API.GameTime
-// {
-// struct GameDateTime
-// {
-// public int elapsedDays;
-// public int time;
-//
-// public GameDateTime(S1GameTime.GameDateTime gameDateTime)
-// {
-//
-// }
-//
-// public void Ad
-// }
-// }
\ No newline at end of file
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using S1GameDateTime = Il2CppScheduleOne.GameTime.GameDateTime;
+using S1TimeManager = Il2CppScheduleOne.GameTime.TimeManager;
+using S1GameDateTimeData = Il2CppScheduleOne.Persistence.Datas.GameDateTimeData;
+#elif (MONOMELON || MONOBEPINEX)
+using S1GameDateTime = ScheduleOne.GameTime.GameDateTime;
+using S1TimeManager = ScheduleOne.GameTime.TimeManager;
+using S1GameDateTimeData = ScheduleOne.Persistence.Datas.GameDateTimeData;
+#endif
+
+using System;
+
+namespace S1API.GameTime
+{
+ ///
+ /// Represents an in-game datetime (elapsed days and 24-hour time).
+ ///
+ public struct GameDateTime
+ {
+ public int ElapsedDays;
+ public int Time;
+
+ ///
+ /// Constructs a GameDateTime from elapsed days and 24-hour time.
+ ///
+ public GameDateTime(int elapsedDays, int time)
+ {
+ ElapsedDays = elapsedDays;
+ Time = time;
+ }
+
+ ///
+ /// Constructs a GameDateTime from total minutes.
+ ///
+ public GameDateTime(int minSum)
+ {
+ ElapsedDays = minSum / 1440;
+ int minutesInDay = minSum % 1440;
+ if (minSum < 0)
+ {
+ minutesInDay = -minSum % 1440;
+ }
+ Time = S1TimeManager.Get24HourTimeFromMinSum(minutesInDay);
+ }
+
+ ///
+ /// Constructs a GameDateTime from an internal GameDateTimeData.
+ ///
+ public GameDateTime(S1GameDateTimeData data)
+ {
+ ElapsedDays = data.ElapsedDays;
+ Time = data.Time;
+ }
+
+ ///
+ /// Constructs a GameDateTime from the internal GameDateTime struct.
+ ///
+ public GameDateTime(S1GameDateTime gameDateTime)
+ {
+ ElapsedDays = gameDateTime.elapsedDays;
+ Time = gameDateTime.time;
+ }
+
+ ///
+ /// Returns the total minute sum (days * 1440 + minutes of day).
+ ///
+ public int GetMinSum()
+ {
+ return ElapsedDays * 1440 + S1TimeManager.GetMinSumFrom24HourTime(Time);
+ }
+
+ ///
+ /// Returns a new GameDateTime with additional minutes added.
+ ///
+ public GameDateTime AddMinutes(int minutes)
+ {
+ return new GameDateTime(GetMinSum() + minutes);
+ }
+
+ ///
+ /// Converts this wrapper to the internal GameDateTime struct.
+ ///
+ public S1GameDateTime ToS1()
+ {
+ return new S1GameDateTime(ElapsedDays, Time);
+ }
+
+ ///
+ /// Returns the current time formatted as a 12-hour AM/PM string.
+ /// Example: "12:30 PM"
+ ///
+ public string GetFormattedTime()
+ {
+ return S1TimeManager.Get12HourTime(Time, true);
+ }
+
+ ///
+ /// Returns true if the time is considered nighttime.
+ /// (Before 6AM or after 6PM)
+ ///
+ public bool IsNightTime()
+ {
+ return Time < 600 || Time >= 1800;
+ }
+
+ ///
+ /// Returns true if the two GameDateTimes are on the same day (ignores time).
+ ///
+ public bool IsSameDay(GameDateTime other)
+ {
+ return ElapsedDays == other.ElapsedDays;
+ }
+
+ ///
+ /// Returns true if the two GameDateTimes are at the same day and time.
+ ///
+ public bool IsSameTime(GameDateTime other)
+ {
+ return ElapsedDays == other.ElapsedDays && Time == other.Time;
+ }
+
+ ///
+ /// String representation: "Day 3, 2:30 PM"
+ ///
+ public override string ToString()
+ {
+ return $"Day {ElapsedDays}, {GetFormattedTime()}";
+ }
+
+ public static GameDateTime operator +(GameDateTime a, GameDateTime b)
+ {
+ return new GameDateTime(a.GetMinSum() + b.GetMinSum());
+ }
+
+ public static GameDateTime operator -(GameDateTime a, GameDateTime b)
+ {
+ return new GameDateTime(a.GetMinSum() - b.GetMinSum());
+ }
+
+ public static bool operator >(GameDateTime a, GameDateTime b)
+ {
+ return a.GetMinSum() > b.GetMinSum();
+ }
+
+ public static bool operator <(GameDateTime a, GameDateTime b)
+ {
+ return a.GetMinSum() < b.GetMinSum();
+ }
+ }
+}
\ No newline at end of file
diff --git a/S1API/GameTime/TimeManager.cs b/S1API/GameTime/TimeManager.cs
index 45f78f9c..e3670802 100644
--- a/S1API/GameTime/TimeManager.cs
+++ b/S1API/GameTime/TimeManager.cs
@@ -1,6 +1,6 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1GameTime = Il2CppScheduleOne.GameTime;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1GameTime = ScheduleOne.GameTime;
#endif
@@ -14,14 +14,129 @@ namespace S1API.GameTime
public static class TimeManager
{
///
- /// Action called when the day passes in-game.
+ /// Called when a new in-game day starts.
///
public static Action OnDayPass = delegate { };
-
+
+ ///
+ /// Called when a new in-game week starts.
+ ///
+ public static Action OnWeekPass = delegate { };
+
+ ///
+ /// Called when the player starts sleeping.
+ ///
+ public static Action OnSleepStart = delegate { };
+
+ ///
+ /// Called when the player finishes sleeping.
+ /// Parameter: total minutes skipped during sleep.
+ ///
+ public static Action OnSleepEnd = delegate { };
+
+ static TimeManager()
+ {
+ if (S1GameTime.TimeManager.Instance != null)
+ {
+ S1GameTime.TimeManager.Instance.onDayPass += (Action)(() => OnDayPass());
+ S1GameTime.TimeManager.Instance.onWeekPass += (Action)(() => OnWeekPass());
+ }
+
+ S1GameTime.TimeManager.onSleepStart += (Action)(() => OnSleepStart());
+ S1GameTime.TimeManager.onSleepEnd += (Action)(minutes => OnSleepEnd(minutes));
+ }
+
+
+ ///
+ /// The current in-game day (Monday, Tuesday, etc.).
+ ///
+ public static Day CurrentDay => (Day)S1GameTime.TimeManager.Instance.CurrentDay;
+
+ ///
+ /// The number of in-game days elapsed.
+ ///
+ public static int ElapsedDays => S1GameTime.TimeManager.Instance.ElapsedDays;
+
+ ///
+ /// The current 24-hour time (e.g., 1330 for 1:30 PM).
+ ///
+ public static int CurrentTime => S1GameTime.TimeManager.Instance.CurrentTime;
+
+ ///
+ /// Whether it is currently nighttime in-game.
+ ///
+ public static bool IsNight => S1GameTime.TimeManager.Instance.IsNight;
+
+ ///
+ /// Whether the game is currently at the end of the day (4:00 AM).
+ ///
+ public static bool IsEndOfDay => S1GameTime.TimeManager.Instance.IsEndOfDay;
+
+ ///
+ /// Whether the player is currently sleeping.
+ ///
+ public static bool SleepInProgress => S1GameTime.TimeManager.Instance.SleepInProgress;
+
+ ///
+ /// Whether the time is currently overridden (frozen or custom).
+ ///
+ public static bool TimeOverridden => S1GameTime.TimeManager.Instance.TimeOverridden;
+
+ ///
+ /// The current normalized time of day (0.0 = start, 1.0 = end).
+ ///
+ public static float NormalizedTime => S1GameTime.TimeManager.Instance.NormalizedTime;
+
+ ///
+ /// Total playtime (in seconds).
+ ///
+ public static float Playtime => S1GameTime.TimeManager.Instance.Playtime;
+
+ ///
+ /// Fast-forwards time to morning wake time (7:00 AM).
+ ///
+ public static void FastForwardToWakeTime() => S1GameTime.TimeManager.Instance.FastForwardToWakeTime();
+
+ ///
+ /// Sets the current time manually.
+ ///
+ public static void SetTime(int time24h, bool local = false) => S1GameTime.TimeManager.Instance.SetTime(time24h, local);
+
+ ///
+ /// Sets the number of elapsed in-game days.
+ ///
+ public static void SetElapsedDays(int days) => S1GameTime.TimeManager.Instance.SetElapsedDays(days);
+
+ ///
+ /// Gets the current time formatted in 12-hour AM/PM format.
+ ///
+ public static string GetFormatted12HourTime()
+ {
+ return S1GameTime.TimeManager.Get12HourTime(CurrentTime, true);
+ }
+
+ ///
+ /// Returns true if the current time is within the specified 24-hour range.
+ ///
+ public static bool IsCurrentTimeWithinRange(int startTime24h, int endTime24h)
+ {
+ return S1GameTime.TimeManager.Instance.IsCurrentTimeWithinRange(startTime24h, endTime24h);
+ }
+
+ ///
+ /// Converts 24-hour time to total minutes.
+ ///
+ public static int GetMinutesFrom24HourTime(int time24h)
+ {
+ return S1GameTime.TimeManager.GetMinSumFrom24HourTime(time24h);
+ }
+
///
- /// The current in-game day.
+ /// Converts total minutes into 24-hour time format.
///
- public static Day CurrentDay =>
- (Day)S1GameTime.TimeManager.Instance.CurrentDay;
+ public static int Get24HourTimeFromMinutes(int minutes)
+ {
+ return S1GameTime.TimeManager.Get24HourTimeFromMinSum(minutes);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Internal/Abstraction/EventHelper.cs b/S1API/Internal/Abstraction/EventHelper.cs
index 1dd94e81..900eeaee 100644
--- a/S1API/Internal/Abstraction/EventHelper.cs
+++ b/S1API/Internal/Abstraction/EventHelper.cs
@@ -36,7 +36,7 @@ internal static void AddListener(Action listener, UnityEvent unityEvent)
/// The event you want to unsubscribe from.
internal static void RemoveListener(Action listener, UnityEvent unityEvent)
{
- SubscribedActions.TryGetValue(listener, out UnityAction wrappedAction);
+ SubscribedActions.TryGetValue(listener, out UnityAction? wrappedAction);
SubscribedActions.Remove(listener);
unityEvent.RemoveListener(wrappedAction);
}
diff --git a/S1API/Internal/Abstraction/ISaveable.cs b/S1API/Internal/Abstraction/ISaveable.cs
index 6ac86151..9d7a1db5 100644
--- a/S1API/Internal/Abstraction/ISaveable.cs
+++ b/S1API/Internal/Abstraction/ISaveable.cs
@@ -1,6 +1,6 @@
-#if (MONO)
+#if (MONOMELON || MONOBEPINEX)
using System.Collections.Generic;
-#elif (IL2CPP)
+#elif (IL2CPPMELON || IL2CPPBEPINEX)
using Il2CppSystem.Collections.Generic;
#endif
@@ -46,4 +46,4 @@ internal interface ISaveable : IRegisterable
Converters = new System.Collections.Generic.List() { new GUIDReferenceConverter() }
};
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Internal/Abstraction/Saveable.cs b/S1API/Internal/Abstraction/Saveable.cs
index b40c61aa..e40d390a 100644
--- a/S1API/Internal/Abstraction/Saveable.cs
+++ b/S1API/Internal/Abstraction/Saveable.cs
@@ -1,6 +1,6 @@
-#if (MONO)
+#if (MONOMELON || MONOBEPINEX)
using System.Collections.Generic;
-#elif (IL2CPP)
+#elif (IL2CPPMELON || IL2CPPBEPINEX)
using Il2CppSystem.Collections.Generic;
#endif
@@ -33,7 +33,7 @@ internal virtual void LoadInternal(string folderPath)
FieldInfo[] saveableFields = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (FieldInfo saveableField in saveableFields)
{
- SaveableField saveableFieldAttribute = saveableField.GetCustomAttribute();
+ SaveableField? saveableFieldAttribute = saveableField.GetCustomAttribute();
if (saveableFieldAttribute == null)
continue;
@@ -68,7 +68,7 @@ internal virtual void SaveInternal(string folderPath, ref List extraSave
FieldInfo[] saveableFields = ReflectionUtils.GetAllFields(GetType(), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (FieldInfo saveableField in saveableFields)
{
- SaveableField saveableFieldAttribute = saveableField.GetCustomAttribute();
+ SaveableField? saveableFieldAttribute = saveableField.GetCustomAttribute();
if (saveableFieldAttribute == null)
continue;
@@ -78,7 +78,7 @@ internal virtual void SaveInternal(string folderPath, ref List extraSave
string saveDataPath = Path.Combine(folderPath, saveFileName);
- object value = saveableField.GetValue(this);
+ object? value = saveableField.GetValue(this);
if (value == null)
// Remove the save if the field is null
File.Delete(saveDataPath);
@@ -119,4 +119,4 @@ void ISaveable.OnSaved() =>
///
protected virtual void OnSaved() { }
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Internal/Patches/HomeScreen.Start.cs b/S1API/Internal/Patches/HomeScreen.Start.cs
new file mode 100644
index 00000000..be76234d
--- /dev/null
+++ b/S1API/Internal/Patches/HomeScreen.Start.cs
@@ -0,0 +1,61 @@
+using System;
+using HarmonyLib;
+using UnityEngine.SceneManagement;
+using S1API.Internal.Utils;
+using S1API.Internal.Abstraction;
+using S1API.PhoneApp;
+using S1API.Logging;
+
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne.UI.Phone;
+#else
+using ScheduleOne.UI.Phone;
+#endif
+
+namespace S1API.Internal.Patches
+{
+ ///
+ /// A Harmony patch for the Start method of the HomeScreen class, facilitating the registration and initialization of PhoneApps.
+ ///
+ [HarmonyPatch(typeof(HomeScreen), "Start")]
+ internal static class HomeScreen_Start_Patch
+ {
+ ///
+ /// A logging instance used for handling log messages pertaining to PhoneApp registration
+ /// and operations. Provides methods to log messages with different severity levels such
+ /// as Info, Warning, Error, and Fatal.
+ ///
+ private static readonly Log Logger = new Log("PhoneApp");
+
+ ///
+ /// Executes after the HomeScreen's Start method to handle the registration
+ /// and initialization of PhoneApps.
+ ///
+ /// The HomeScreen instance being targeted in the patch.
+ static void Postfix(HomeScreen __instance)
+ {
+ if (__instance == null)
+ return;
+
+ // Re-register all PhoneApps
+ var phoneApps = ReflectionUtils.GetDerivedClasses();
+ foreach (var type in phoneApps)
+ {
+ if (type.GetConstructor(Type.EmptyTypes) == null)
+ continue;
+
+ try
+ {
+ var instance = (PhoneApp.PhoneApp)Activator.CreateInstance(type)!;
+ ((IRegisterable)instance).CreateInternal();
+ instance.SpawnUI(__instance);
+ instance.SpawnIcon(__instance);
+ }
+ catch (Exception e)
+ {
+ Logger.Warning($"[PhoneApp] Failed to register {type.FullName}: {e.Message}");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Internal/Patches/NPCPatches.cs b/S1API/Internal/Patches/NPCPatches.cs
index c4304982..0c06a5bf 100644
--- a/S1API/Internal/Patches/NPCPatches.cs
+++ b/S1API/Internal/Patches/NPCPatches.cs
@@ -1,8 +1,8 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1Loaders = Il2CppScheduleOne.Persistence.Loaders;
using S1NPCs = Il2CppScheduleOne.NPCs;
using Il2CppSystem.Collections.Generic;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1Loaders = ScheduleOne.Persistence.Loaders;
using S1NPCs = ScheduleOne.NPCs;
using System.Collections.Generic;
@@ -11,9 +11,10 @@
using System;
using System.IO;
using System.Linq;
+using System.Reflection;
using HarmonyLib;
+using S1API.Entities;
using S1API.Internal.Utils;
-using S1API.NPCs;
namespace S1API.Internal.Patches
{
@@ -23,13 +24,6 @@ namespace S1API.Internal.Patches
[HarmonyPatch]
internal class NPCPatches
{
-
- // ReSharper disable once RedundantNameQualifier
- ///
- /// List of all custom NPCs currently created.
- ///
- private static readonly System.Collections.Generic.List NPCs = new System.Collections.Generic.List();
-
///
/// Patching performed for when game NPCs are loaded.
///
@@ -41,13 +35,19 @@ private static void NPCsLoadersLoad(S1Loaders.NPCsLoader __instance, string main
{
foreach (Type type in ReflectionUtils.GetDerivedClasses())
{
- NPC customNPC = (NPC)Activator.CreateInstance(type);
- NPCs.Add(customNPC);
+ NPC? customNPC = (NPC)Activator.CreateInstance(type, true)!;
+ if (customNPC == null)
+ throw new Exception($"Unable to create instance of {type.FullName}!");
+
+ // We skip any S1API NPCs, as they are base NPC wrappers.
+ if (type.Assembly == Assembly.GetExecutingAssembly())
+ continue;
+
string npcPath = Path.Combine(mainPath, customNPC.S1NPC.SaveFolderName);
customNPC.LoadInternal(npcPath);
}
}
-
+
///
/// Patching performed for when a single NPC starts (including modded in NPCs).
///
@@ -55,7 +55,8 @@ 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)?.CreateInternal();
+ NPC.All.FirstOrDefault(npc => npc.IsCustomNPC && npc.S1NPC == __instance)?.CreateInternal();
+
///
/// Patching performed for when an NPC calls to save data.
@@ -66,7 +67,7 @@ private static void NPCStart(S1NPCs.NPC __instance) =>
[HarmonyPatch(typeof(S1NPCs.NPC), "WriteData")]
[HarmonyPostfix]
private static void NPCWriteData(S1NPCs.NPC __instance, string parentFolderPath, ref List __result) =>
- NPCs.FirstOrDefault(npc => npc.S1NPC == __instance)?.SaveInternal(parentFolderPath, ref __result);
+ NPC.All.FirstOrDefault(npc => npc.IsCustomNPC && npc.S1NPC == __instance)?.SaveInternal(parentFolderPath, ref __result);
///
/// Patching performed for when an NPC is destroyed.
@@ -74,14 +75,7 @@ private static void NPCWriteData(S1NPCs.NPC __instance, string parentFolderPath,
/// Instance of the NPC
[HarmonyPatch(typeof(S1NPCs.NPC), "OnDestroy")]
[HarmonyPostfix]
- private static void NPCOnDestroy(S1NPCs.NPC __instance)
- {
- NPCs.RemoveAll(npc => npc.S1NPC == __instance);
- NPC? npc = NPCs.FirstOrDefault(npc => npc.S1NPC == __instance);
- if (npc == null)
- return;
-
- NPCs.Remove(npc);
- }
+ private static void NPCOnDestroy(S1NPCs.NPC __instance) =>
+ NPC.All.Remove(NPC.All.First(npc => npc.S1NPC == __instance));
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Internal/Patches/PhoneAppPatches.cs b/S1API/Internal/Patches/PhoneAppPatches.cs
deleted file mode 100644
index 30804bfb..00000000
--- a/S1API/Internal/Patches/PhoneAppPatches.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using System;
-using HarmonyLib;
-using UnityEngine.SceneManagement;
-using S1API.Internal.Utils;
-using S1API.Internal.Abstraction;
-using S1API.PhoneApp;
-
-namespace S1API.Internal.Patches
-{
-#if IL2CPP
- [HarmonyPatch(typeof(SceneManager), nameof(SceneManager.Internal_SceneLoaded))]
-#else
- [HarmonyPatch(typeof(SceneManager), "Internal_SceneLoaded", new Type[] { typeof(Scene), typeof(LoadSceneMode) })]
-
-#endif
- internal static class PhoneAppPatches
- {
- private static bool _loaded = false;
-
- ///
- /// Executes logic after the Unity SceneManager completes loading a scene.
- /// Registers all derived implementations of the PhoneApp class during the loading
- /// process of the "Main" scene.
- ///
- /// The scene that has been loaded.
- /// The loading mode used by the SceneManager.
- static void Postfix(Scene scene, LoadSceneMode mode)
- {
- if (scene.name != "Main") return;
-
- // Re-register all PhoneApps every time the Main scene loads
- var phoneApp = ReflectionUtils.GetDerivedClasses();
- foreach (var type in phoneApp)
- {
- if (type.GetConstructor(Type.EmptyTypes) == null) continue;
-
- try
- {
- var instance = (PhoneApp.PhoneApp)Activator.CreateInstance(type)!;
- ((IRegisterable)instance).CreateInternal();
- }
- catch (System.Exception e)
- {
- MelonLoader.MelonLogger.Warning($"[PhoneApp] Failed to register {type.FullName}: {e.Message}");
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/S1API/Internal/Patches/PhoneAppRegistry.cs b/S1API/Internal/Patches/PhoneAppRegistry.cs
new file mode 100644
index 00000000..af62c2b3
--- /dev/null
+++ b/S1API/Internal/Patches/PhoneAppRegistry.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+
+namespace S1API.Internal.Patches
+{
+ ///
+ /// Provides functionality for managing the registration of custom phone applications.
+ ///
+ ///
+ /// This static class serves as a registry for tracking instances of phone applications.
+ /// Applications are added to a centralized list which can then be used for initialization or
+ /// interacting with registered applications at runtime.
+ ///
+ internal static class PhoneAppRegistry
+ {
+ ///
+ /// A static readonly list that stores instances of phone applications registered via the PhoneAppRegistry.
+ ///
+ ///
+ /// This list holds all registered instances of objects. Applications are added to this collection
+ /// whenever they are registered using the PhoneAppRegistry.Register method, which is typically called automatically
+ /// during the application's lifecycle.
+ /// It serves as a central repository for all in-game phone applications, enabling other systems to access and manage
+ /// these registered apps efficiently.
+ ///
+ public static readonly List RegisteredApps = new List();
+
+ ///
+ /// Registers a specified phone app into the phone application registry.
+ ///
+ /// The PhoneApp instance to be registered.
+ public static void Register(PhoneApp.PhoneApp app)
+ {
+ RegisteredApps.Add(app);
+ }
+ }
+}
\ No newline at end of file
diff --git a/S1API/Internal/Patches/PlayerPatches.cs b/S1API/Internal/Patches/PlayerPatches.cs
new file mode 100644
index 00000000..79395e16
--- /dev/null
+++ b/S1API/Internal/Patches/PlayerPatches.cs
@@ -0,0 +1,39 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using S1PlayerScripts = Il2CppScheduleOne.PlayerScripts;
+#else
+using S1PlayerScripts = ScheduleOne.PlayerScripts;
+#endif
+
+
+using System.Linq;
+using HarmonyLib;
+using S1API.Entities;
+
+namespace S1API.Internal.Patches
+{
+ ///
+ /// INTERNAL: Patches to apply to the Players for tracking.
+ ///
+ [HarmonyPatch]
+ internal class PlayerPatches
+ {
+ ///
+ /// INTERNAL: Adds players to the player list upon wake.
+ ///
+ /// The player to add.
+ [HarmonyPatch(typeof(S1PlayerScripts.Player), "Awake")]
+ [HarmonyPostfix]
+ private static void PlayerAwake(S1PlayerScripts.Player __instance) =>
+ new Player(__instance);
+
+
+ ///
+ /// INTERNAL: Removes players from the player list upon destruction.
+ ///
+ /// The player to remove.
+ [HarmonyPatch(typeof(S1PlayerScripts.Player), "OnDestroy")]
+ [HarmonyPostfix]
+ private static void PlayerOnDestroy(S1PlayerScripts.Player __instance) =>
+ Player.All.Remove(Player.All.First(player => player.S1Player == __instance));
+ }
+}
\ No newline at end of file
diff --git a/S1API/Internal/Patches/QuestPatches.cs b/S1API/Internal/Patches/QuestPatches.cs
index 6e9fd49a..035bbe64 100644
--- a/S1API/Internal/Patches/QuestPatches.cs
+++ b/S1API/Internal/Patches/QuestPatches.cs
@@ -1,9 +1,9 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1Loaders = Il2CppScheduleOne.Persistence.Loaders;
using S1Datas = Il2CppScheduleOne.Persistence.Datas;
using S1Quests = Il2CppScheduleOne.Quests;
using Il2CppSystem.Collections.Generic;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1Loaders = ScheduleOne.Persistence.Loaders;
using S1Datas = ScheduleOne.Persistence.Datas;
using S1Quests = ScheduleOne.Quests;
@@ -59,8 +59,8 @@ private static void QuestsLoaderLoad(S1Loaders.QuestsLoader __instance, string m
string[] questDirectories = Directory.GetDirectories(mainPath)
.Select(Path.GetFileName)
- .Where(directory => directory.StartsWith("Quest_"))
- .ToArray();
+ .Where(directory => directory != null && directory.StartsWith("Quest_"))
+ .ToArray()!;
foreach (string questDirectory in questDirectories)
{
@@ -128,4 +128,4 @@ private static void QuestStart(S1Quests.Quest __instance) =>
// NPCs.Remove(npc);
// }
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Internal/Patches/TimePatches.cs b/S1API/Internal/Patches/TimePatches.cs
index 34a3f176..32712fbe 100644
--- a/S1API/Internal/Patches/TimePatches.cs
+++ b/S1API/Internal/Patches/TimePatches.cs
@@ -1,6 +1,6 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1GameTime = Il2CppScheduleOne.GameTime;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1GameTime = ScheduleOne.GameTime;
#endif
@@ -33,4 +33,4 @@ void DayPass()
__instance.onDayPass += (Action)DayPass;
}
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Internal/Utils/ButtonListener.cs b/S1API/Internal/Utils/ButtonListener.cs
index 75f6a75a..8f8d6ab3 100644
--- a/S1API/Internal/Utils/ButtonListener.cs
+++ b/S1API/Internal/Utils/ButtonListener.cs
@@ -1,69 +1,76 @@
-using UnityEngine.UI;
using System;
using S1API.Internal.Abstraction;
using UnityEngine;
+using UnityEngine.UI;
-public static class ButtonUtils
+namespace S1API.Internal.Utils
{
///
- /// Adds a click listener to the specified button, ensuring compatibility with IL2CPP and Mono.
+ /// Utility class for managing UI Buttons.
+ /// TODO (@omar-akermi): Is this intended to be internal instead of public??
///
- public static void AddListener(Button button, Action action)
+ public static class ButtonUtils
{
- if (button == null || action == null) return;
- EventHelper.AddListener(action, button.onClick);
- }
+ ///
+ /// Adds a click listener to the specified button, ensuring compatibility with IL2CPP and Mono.
+ ///
+ public static void AddListener(Button button, Action action)
+ {
+ if (button == null || action == null) return;
+ EventHelper.AddListener(action, button.onClick);
+ }
- ///
- /// Removes a previously added click listener from the specified button.
- ///
- public static void RemoveListener(Button button, Action action)
- {
- if (button == null || action == null) return;
- EventHelper.RemoveListener(action, button.onClick);
- }
+ ///
+ /// Removes a previously added click listener from the specified button.
+ ///
+ public static void RemoveListener(Button button, Action action)
+ {
+ if (button == null || action == null) return;
+ EventHelper.RemoveListener(action, button.onClick);
+ }
- ///
- /// Removes all listeners from the specified button safely.
- ///
- public static void ClearListeners(Button button)
- {
- if (button == null) return;
- button.onClick.RemoveAllListeners();
- }
+ ///
+ /// Removes all listeners from the specified button safely.
+ ///
+ public static void ClearListeners(Button button)
+ {
+ if (button == null) return;
+ button.onClick.RemoveAllListeners();
+ }
- ///
- /// Enables the button and optionally updates the label.
- ///
- public static void Enable(Button button, Text label = null, string text = null)
- {
- if (button != null) button.interactable = true;
- if (label != null && !string.IsNullOrEmpty(text)) label.text = text;
- }
+ ///
+ /// Enables the button and optionally updates the label.
+ ///
+ public static void Enable(Button button, Text? label = null, string? text = null)
+ {
+ if (button != null) button.interactable = true;
+ if (label != null && !string.IsNullOrEmpty(text)) label.text = text;
+ }
- ///
- /// Disables the button and optionally updates the label.
- ///
- public static void Disable(Button button, Text label = null, string text = null)
- {
- if (button != null) button.interactable = false;
- if (label != null && !string.IsNullOrEmpty(text)) label.text = text;
- }
+ ///
+ /// Disables the button and optionally updates the label.
+ ///
+ public static void Disable(Button button, Text? label = null, string? text = null)
+ {
+ if (button != null) button.interactable = false;
+ if (label != null && !string.IsNullOrEmpty(text)) label.text = text;
+ }
- ///
- /// Sets the label text of a button with a known Text child.
- ///
- public static void SetLabel(Text label, string text)
- {
- if (label != null) label.text = text;
- }
- ///
- /// Sets the button label and background color.
- ///
- public static void SetStyle(Button button, Text label, string text, Color bg)
- {
- if (button == null || label == null) return;
- label.text = text;
- button.image.color = bg;
+ ///
+ /// Sets the label text of a button with a known Text child.
+ ///
+ public static void SetLabel(Text label, string text)
+ {
+ if (label != null) label.text = text;
+ }
+ ///
+ /// Sets the button label and background color.
+ ///
+ public static void SetStyle(Button button, Text label, string text, Color bg)
+ {
+ if (button == null || label == null) return;
+ label.text = text;
+ button.image.color = bg;
+ }
}
}
\ No newline at end of file
diff --git a/S1API/Internal/Utils/CrossType.cs b/S1API/Internal/Utils/CrossType.cs
index 5ce09684..f1d0bdad 100644
--- a/S1API/Internal/Utils/CrossType.cs
+++ b/S1API/Internal/Utils/CrossType.cs
@@ -1,6 +1,6 @@
-#if (MONO)
+#if (MONOMELON || MONOBEPINEX)
using System;
-# elif (IL2CPP)
+# elif (IL2CPPMELON || IL2CPPBEPINEX)
using Il2CppSystem;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
@@ -21,9 +21,9 @@ internal static class CrossType
/// The type of the class.
internal static Type Of()
{
-#if (MONO)
+#if (MONOMELON || MONOBEPINEX)
return typeof(T);
-# elif (IL2CPP)
+# elif (IL2CPPMELON || IL2CPPBEPINEX)
return Il2CppType.Of();
#endif
}
@@ -36,13 +36,13 @@ internal static Type Of()
/// The class we're checking against.
/// Whether obj is of class T or not.
internal static bool Is(object obj, out T result)
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
where T : Il2CppObjectBase
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
where T : class
#endif
{
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
if (obj is Object il2CppObj)
{
Type il2CppType = Il2CppType.Of();
@@ -52,7 +52,7 @@ internal static bool Is(object obj, out T result)
return true;
}
}
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
if (obj is T t)
{
result = t;
@@ -71,16 +71,16 @@ internal static bool Is(object obj, out T result)
/// The type to cast to.
/// The object cast to the specified type.
internal static T As(object obj)
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
where T : Il2CppObjectBase
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
where T : class
#endif
=>
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
obj is Object il2CppObj ? il2CppObj.Cast() : null!;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
(T)obj;
#endif
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Internal/Utils/ImageUtils.cs b/S1API/Internal/Utils/ImageUtils.cs
index 1f072b6f..62365758 100644
--- a/S1API/Internal/Utils/ImageUtils.cs
+++ b/S1API/Internal/Utils/ImageUtils.cs
@@ -1,14 +1,17 @@
-using MelonLoader;
-using System;
-using System.Collections.Generic;
+using S1API.Logging;
using System.IO;
-using System.Text;
using UnityEngine;
namespace S1API.Internal.Utils
{
+ ///
+ /// A utility class to assist with loading images into the game.
+ /// Useful for icons such as on phone apps, custom NPCs, quests, etc.
+ ///
public static class ImageUtils
{
+ private static readonly Log _loggerInstance = new Log("ImageUtils");
+
///
/// Loads an image from the specified file path and converts it into a Sprite object.
///
@@ -16,12 +19,12 @@ public static class ImageUtils
///
/// A Sprite object representing the loaded image, or null if the image could not be loaded or the file does not exist.
///
- public static Sprite LoadImage(string fileName)
+ public static Sprite? LoadImage(string fileName)
{
string fullPath = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? string.Empty, fileName);
if (!File.Exists(fullPath))
{
- MelonLogger.Error($"❌ Icon file not found: {fullPath}");
+ _loggerInstance.Error($"❌ Icon file not found: {fullPath}");
return null;
}
@@ -36,10 +39,10 @@ public static Sprite LoadImage(string fileName)
}
catch (System.Exception ex)
{
- MelonLogger.Error("❌ Failed to load sprite: " + ex);
+ _loggerInstance.Error("❌ Failed to load sprite: " + ex);
}
return null;
}
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Internal/Utils/RandomUtils.cs b/S1API/Internal/Utils/RandomUtils.cs
index a1d984ac..f57dc030 100644
--- a/S1API/Internal/Utils/RandomUtils.cs
+++ b/S1API/Internal/Utils/RandomUtils.cs
@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
-using UnityEngine;
-namespace S1API.Utils
+namespace S1API.Internal.Utils
{
///
/// A utility class providing random selection functionality for lists and numeric ranges.
@@ -17,7 +16,7 @@ public static class RandomUtils
public static T PickOne(this IList list)
{
if (list == null || list.Count == 0)
- return default;
+ return default!;
return list[UnityEngine.Random.Range(0, list.Count)];
}
@@ -32,8 +31,8 @@ public static T PickOne(this IList list)
/// A randomly selected item that satisfies the condition, or the default value of the type if no valid item is found.
public static T PickUnique(this IList list, Func isDuplicate, int maxTries = 10)
{
- if (list == null || list.Count == 0)
- return default;
+ if (list.Count == 0)
+ return default!;
for (int i = 0; i < maxTries; i++)
{
@@ -42,7 +41,7 @@ public static T PickUnique(this IList list, Func isDuplicate, int
return item;
}
- return default;
+ return default!;
}
///
@@ -54,7 +53,9 @@ public static T PickUnique(this IList list, Func isDuplicate, int
/// A list containing the selected random items, or an empty list if the input list is null or empty.
public static List PickMany(this IList list, int count)
{
- if (list == null || list.Count == 0) return new List();
+ if (list.Count == 0)
+ return new List();
+
var copy = new List(list);
var result = new List();
diff --git a/S1API/Internal/Utils/ReflectionUtils.cs b/S1API/Internal/Utils/ReflectionUtils.cs
index ff22e152..664c6e7d 100644
--- a/S1API/Internal/Utils/ReflectionUtils.cs
+++ b/S1API/Internal/Utils/ReflectionUtils.cs
@@ -19,12 +19,12 @@ internal static List GetDerivedClasses()
{
List derivedClasses = new List();
Assembly[] applicableAssemblies = AppDomain.CurrentDomain.GetAssemblies()
- .Where(assembly => !assembly.FullName.StartsWith("System") &&
- !assembly.FullName.StartsWith("Unity") &&
- !assembly.FullName.StartsWith("Il2Cpp") &&
- !assembly.FullName.StartsWith("mscorlib") &&
- !assembly.FullName.StartsWith("Mono.") &&
- !assembly.FullName.StartsWith("netstandard"))
+ .Where(assembly => !assembly.FullName!.StartsWith("System") &&
+ !assembly.FullName!.StartsWith("Unity") &&
+ !assembly.FullName!.StartsWith("Il2Cpp") &&
+ !assembly.FullName!.StartsWith("mscorlib") &&
+ !assembly.FullName!.StartsWith("Mono.") &&
+ !assembly.FullName!.StartsWith("netstandard"))
.ToArray();
foreach (Assembly assembly in applicableAssemblies)
derivedClasses.AddRange(assembly.GetTypes()
diff --git a/S1API/Items/ItemCategory.cs b/S1API/Items/ItemCategory.cs
index 483a6ed2..a4e88df8 100644
--- a/S1API/Items/ItemCategory.cs
+++ b/S1API/Items/ItemCategory.cs
@@ -5,17 +5,66 @@
///
public enum ItemCategory
{
+ ///
+ /// Represents items such as Cocaine, Weed, etc.
+ /// Oddly, SpeedGrow is in this category as of (v0.3.4f8).
+ ///
Product,
+
+ ///
+ /// Represents items such as Baggies, Bricks, Jars, etc.
+ ///
Packaging,
+
+ ///
+ /// Represents items such as Soil, Fertilizer, Pots, etc.
+ ///
Growing,
+
+ ///
+ /// Represents equipment tools such as the clippers.
+ /// Oddly, trash bags is in this category as of (v0.3.4f8).
+ ///
Tools,
+
+ ///
+ /// Represents items such as TV, Trash Can, Bed, etc.
+ ///
Furniture,
+
+ ///
+ /// Represents items such as Floor Lamps, Halogen Lights, etc.
+ ///
Lighting,
+
+ ///
+ /// Represents cash-based items.
+ ///
Cash,
+
+ ///
+ /// Represents items such as Cuke, Energy Drink, etc.
+ ///
Consumable,
+
+ ///
+ /// Represents items such as Drying Rack, Brick Press, Mixing Station, etc.
+ ///
Equipment,
+
+ ///
+ /// Represents items such as Acid, Banana, Chili, etc.
+ ///
Ingredient,
+
+ ///
+ /// Represents items such as GoldBar, WallClock, WoodSign, etc.
+ ///
Decoration,
+
+ ///
+ /// Represents clothing items.
+ ///
Clothing
}
}
\ No newline at end of file
diff --git a/S1API/Items/ItemDefinition.cs b/S1API/Items/ItemDefinition.cs
index b7884c67..cff1924b 100644
--- a/S1API/Items/ItemDefinition.cs
+++ b/S1API/Items/ItemDefinition.cs
@@ -1,6 +1,6 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
@@ -115,4 +115,4 @@ public override int GetHashCode() =>
public virtual ItemInstance CreateInstance(int quantity = 1) =>
new ItemInstance(S1ItemDefinition.GetDefaultInstance(quantity));
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Items/ItemInstance.cs b/S1API/Items/ItemInstance.cs
index 5d498184..2fdb7f35 100644
--- a/S1API/Items/ItemInstance.cs
+++ b/S1API/Items/ItemInstance.cs
@@ -1,6 +1,6 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
@@ -32,4 +32,4 @@ internal ItemInstance(S1ItemFramework.ItemInstance itemInstance) =>
public ItemDefinition Definition =>
new ItemDefinition(S1ItemInstance.Definition);
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Items/ItemManager.cs b/S1API/Items/ItemManager.cs
index 10551c92..e886c6df 100644
--- a/S1API/Items/ItemManager.cs
+++ b/S1API/Items/ItemManager.cs
@@ -1,8 +1,8 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1 = Il2CppScheduleOne;
using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
using S1Product = Il2CppScheduleOne.Product;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1 = ScheduleOne;
using S1ItemFramework = ScheduleOne.ItemFramework;
using S1Product = ScheduleOne.Product;
@@ -39,4 +39,4 @@ public static ItemDefinition GetItemDefinition(string itemID)
return new ItemDefinition(itemDefinition);
}
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Items/ItemSlotInstance.cs b/S1API/Items/ItemSlotInstance.cs
index 5b6d750b..ad51429d 100644
--- a/S1API/Items/ItemSlotInstance.cs
+++ b/S1API/Items/ItemSlotInstance.cs
@@ -1,7 +1,7 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
using S1Product = Il2CppScheduleOne.Product;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1ItemFramework = ScheduleOne.ItemFramework;
using S1Product = ScheduleOne.Product;
#endif
@@ -67,4 +67,4 @@ public ItemInstance? ItemInstance
public void AddQuantity(int amount) =>
S1ItemSlot.ChangeQuantity(amount);
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Leveling/LevelManager.cs b/S1API/Leveling/LevelManager.cs
index c9a8ac8e..5bbc31a1 100644
--- a/S1API/Leveling/LevelManager.cs
+++ b/S1API/Leveling/LevelManager.cs
@@ -1,6 +1,6 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1Levelling = Il2CppScheduleOne.Levelling;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1Levelling = ScheduleOne.Levelling;
#endif
@@ -16,4 +16,4 @@ public static class LevelManager
///
public static Rank Rank = (Rank)S1Levelling.LevelManager.Instance.Rank;
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Leveling/Rank.cs b/S1API/Leveling/Rank.cs
index 1582ee51..9144b28f 100644
--- a/S1API/Leveling/Rank.cs
+++ b/S1API/Leveling/Rank.cs
@@ -5,16 +5,59 @@
///
public enum Rank
{
+ ///
+ /// Represents the first rank for the player.
+ ///
StreetRat,
+
+ ///
+ /// Represents the second rank for the player.
+ ///
Hoodlum,
+
+ ///
+ /// Represents the third rank for the player.
+ ///
Peddler,
+
+ ///
+ /// Represents the fourth rank for the player.
+ ///
Hustler,
+
+ ///
+ /// Represents the fifth rank for the player.
+ ///
Bagman,
+
+ ///
+ /// Represents the sixth rank for the player.
+ ///
Enforcer,
+
+ ///
+ /// Represents the seventh rank for the player.
+ ///
ShotCaller,
+
+ ///
+ /// Represents the eighth rank for the player.
+ ///
BlockBoss,
+
+ ///
+ /// Represents the ninth rank for the player.
+ ///
Underlord,
+
+ ///
+ /// Represents the tenth rank for the player.
+ ///
Baron,
+
+ ///
+ /// Represents the eleventh rank for the player.
+ ///
Kingpin
}
}
\ No newline at end of file
diff --git a/S1API/Logging/Log.cs b/S1API/Logging/Log.cs
new file mode 100644
index 00000000..031fe680
--- /dev/null
+++ b/S1API/Logging/Log.cs
@@ -0,0 +1,96 @@
+#if (MONOMELON || IL2CPPMELON)
+using MelonLoader;
+#else
+using BepInEx.Logging;
+#endif
+
+namespace S1API.Logging
+{
+ ///
+ /// Centralized Logging class that handles both BepInEx and MelonLoader logging.
+ ///
+ public class Log
+ {
+#if (MONOMELON || IL2CPPMELON)
+ private readonly MelonLogger.Instance _loggerInstance;
+#else
+ private readonly ManualLogSource _loggerInstance;
+#endif
+
+ ///
+ /// Default constructor for instance
+ ///
+ /// The source name to use for logging
+ public Log(string sourceName)
+ {
+#if (MONOMELON || IL2CPPMELON)
+ _loggerInstance = new MelonLogger.Instance(sourceName);
+#else
+ _loggerInstance = Logger.CreateLogSource(sourceName);
+#endif
+ }
+
+#if (MONOBEPINEX || IL2CPPBEPINEX)
+ ///
+ /// Default constructor for instance when BepInEx is enabled
+ ///
+ /// Existing instance to use
+ public Log(ManualLogSource loggerInstance)
+ {
+ _loggerInstance = loggerInstance;
+ }
+#endif
+
+ ///
+ /// Logs a message with Info level
+ ///
+ /// Message to log
+ public void Msg(string message)
+ {
+#if (MONOMELON || IL2CPPMELON)
+ _loggerInstance.Msg(message);
+#else
+ _loggerInstance.LogInfo(message);
+#endif
+ }
+
+ ///
+ /// Logs a message with Warning level
+ ///
+ /// Message to log
+ public void Warning(string message)
+ {
+#if (MONOMELON || IL2CPPMELON)
+ _loggerInstance.Warning(message);
+#else
+ _loggerInstance.LogWarning(message);
+#endif
+ }
+
+ ///
+ /// Logs a message with Error level
+ ///
+ /// Message to log
+ public void Error(string message)
+ {
+#if (MONOMELON || IL2CPPMELON)
+ _loggerInstance.Error(message);
+#else
+ _loggerInstance.LogError(message);
+#endif
+ }
+
+ ///
+ /// Logs a message with Fatal level
+ ///
+ /// Message to log
+ public void BigError(string message)
+ {
+#if (MONOMELON || IL2CPPMELON)
+ _loggerInstance.BigError(message);
+#else
+ _loggerInstance.LogFatal(message);
+#endif
+ }
+ }
+}
diff --git a/S1API/Map/Region.cs b/S1API/Map/Region.cs
new file mode 100644
index 00000000..1ed6785b
--- /dev/null
+++ b/S1API/Map/Region.cs
@@ -0,0 +1,38 @@
+namespace S1API.Map
+{
+ ///
+ /// The regions available in the game.
+ ///
+ public enum Region
+ {
+ ///
+ /// The first region in the game.
+ ///
+ Northtown,
+
+ ///
+ /// The second region in the game.
+ ///
+ Westville,
+
+ ///
+ /// The third region in the game.
+ ///
+ Downtown,
+
+ ///
+ /// The fourth region in the game.
+ ///
+ Docks,
+
+ ///
+ /// The fifth region in the game.
+ ///
+ Suburbia,
+
+ ///
+ /// The sixth region in the game.
+ ///
+ Uptown
+ }
+}
\ No newline at end of file
diff --git a/S1API/NPCs/Response.cs b/S1API/Messaging/Response.cs
similarity index 94%
rename from S1API/NPCs/Response.cs
rename to S1API/Messaging/Response.cs
index 100dc6b7..d0a2d021 100644
--- a/S1API/NPCs/Response.cs
+++ b/S1API/Messaging/Response.cs
@@ -1,11 +1,11 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1Messaging = Il2CppScheduleOne.Messaging;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1Messaging = ScheduleOne.Messaging;
#endif
using System;
-namespace S1API.NPCs
+namespace S1API.Messaging
{
///
/// Represents a message response displayed for the player.
diff --git a/S1API/Money/CashDefinition.cs b/S1API/Money/CashDefinition.cs
index 67186f63..47cc026d 100644
--- a/S1API/Money/CashDefinition.cs
+++ b/S1API/Money/CashDefinition.cs
@@ -1,6 +1,6 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
@@ -35,4 +35,4 @@ internal CashDefinition(S1ItemFramework.CashDefinition s1ItemDefinition) : base(
public override ItemInstance CreateInstance(int quantity = 1) =>
new CashInstance(S1CashDefinition.GetDefaultInstance(quantity));
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Money/CashInstance.cs b/S1API/Money/CashInstance.cs
index f555c355..a9b96e9c 100644
--- a/S1API/Money/CashInstance.cs
+++ b/S1API/Money/CashInstance.cs
@@ -1,6 +1,6 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1ItemFramework = ScheduleOne.ItemFramework;
#endif
@@ -42,4 +42,4 @@ public void AddQuantity(float amount) =>
public void SetQuantity(float newQuantity) =>
S1CashInstance.SetBalance(newQuantity);
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Money/Money.cs b/S1API/Money/Money.cs
new file mode 100644
index 00000000..d886dc5b
--- /dev/null
+++ b/S1API/Money/Money.cs
@@ -0,0 +1,132 @@
+#if (IL2CPPMELON || IL2CPPBEPINEX)
+using Il2CppScheduleOne;
+using S1ItemFramework = Il2CppScheduleOne.ItemFramework;
+using S1MoneyFramework = Il2CppScheduleOne.Money;
+#else
+using ScheduleOne;
+using S1ItemFramework = ScheduleOne.ItemFramework;
+using S1MoneyFramework = ScheduleOne.Money;
+
+#endif
+
+using S1API.Internal.Utils;
+using System;
+using UnityEngine;
+
+namespace S1API.Money
+{
+ ///
+ /// Provides static access to financial operations, including methods for managing cash balance,
+ /// creating online transactions, and calculating net worth.
+ ///
+ public static class Money
+ {
+ ///
+ /// Provides internal access to the underlying financial operations manager.
+ /// This property is utilized for interacting with the core financial functionality.
+ ///
+ private static S1MoneyFramework.MoneyManager Internal => S1MoneyFramework.MoneyManager.Instance;
+
+ ///
+ /// Event triggered whenever there is a change in the balance,
+ /// including cash balance or online transactions.
+ ///
+ public static event Action? OnBalanceChanged;
+
+ ///
+ /// Adjusts the cash balance by the specified amount.
+ ///
+ /// The amount to modify the cash balance by. Positive values increase the balance, and negative values decrease it.
+ /// Indicates whether the cash change should be visualized on the HUD.
+ /// Indicates whether a sound effect should be played to signify the cash adjustment.
+ public static void ChangeCashBalance(float amount, bool visualizeChange = true, bool playCashSound = false)
+ {
+ Internal?.ChangeCashBalance(amount, visualizeChange, playCashSound);
+ OnBalanceChanged?.Invoke();
+ }
+
+ ///
+ /// Creates an online transaction.
+ ///
+ /// The name of the transaction.
+ /// The monetary amount per unit involved in the transaction.
+ /// The number of units in the transaction.
+ /// An optional note or description for the transaction.
+ public static void CreateOnlineTransaction(string transactionName, float unitAmount, float quantity,
+ string transactionNote)
+ {
+ Internal?.CreateOnlineTransaction(transactionName, unitAmount, quantity, transactionNote);
+ OnBalanceChanged?.Invoke();
+ }
+
+ ///
+ /// Retrieves the total net worth, including all cash and online balances combined.
+ ///
+ /// The total net worth as a floating-point value.
+ public static float GetNetWorth()
+ {
+ return Internal != null ? Internal.GetNetWorth() : 0f;
+ }
+
+ ///
+ /// Retrieves the current cash balance.
+ ///
+ /// The current cash balance as a floating-point value.
+ public static float GetCashBalance()
+ {
+ return Internal != null ? Internal.cashBalance : 0f;
+ }
+
+ ///
+ /// Retrieves the current online balance.
+ ///
+ /// The current amount of online balance.
+ public static float GetOnlineBalance()
+ {
+ return Internal != null ? Internal.sync___get_value_onlineBalance() : 0f;
+ }
+
+ ///
+ /// Registers a callback to be invoked during net worth calculation.
+ ///
+ /// The callback to be executed when net worth is calculated. It receives an object as its parameter.
+ public static void AddNetworthCalculation(System.Action
public void End() => S1Quest?.End();
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Quests/QuestEntry.cs b/S1API/Quests/QuestEntry.cs
index 12bc91c8..44a7ac1d 100644
--- a/S1API/Quests/QuestEntry.cs
+++ b/S1API/Quests/QuestEntry.cs
@@ -1,6 +1,6 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1Quests = Il2CppScheduleOne.Quests;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1Quests = ScheduleOne.Quests;
#endif
@@ -76,4 +76,4 @@ public Vector3 POIPosition
public void SetState(QuestState questState) =>
S1QuestEntry.SetState((S1Quests.EQuestState)questState);
}
-}
\ No newline at end of file
+}
diff --git a/S1API/Quests/QuestManager.cs b/S1API/Quests/QuestManager.cs
index bf15845d..36e694f9 100644
--- a/S1API/Quests/QuestManager.cs
+++ b/S1API/Quests/QuestManager.cs
@@ -29,7 +29,10 @@ public static Quest CreateQuest(string? guid = null) where T : Quest =>
///
public static Quest CreateQuest(Type questType, string? guid = null)
{
- Quest quest = (Quest)Activator.CreateInstance(questType);
+ Quest? quest = (Quest)Activator.CreateInstance(questType)!;
+ if (quest == null)
+ throw new Exception($"Unable to create quest instance of {questType.FullName}!");
+
Quests.Add(quest);
return quest;
}
diff --git a/S1API/Quests/QuestState.cs b/S1API/Quests/QuestState.cs
index 8f02c675..d907b328 100644
--- a/S1API/Quests/QuestState.cs
+++ b/S1API/Quests/QuestState.cs
@@ -5,11 +5,34 @@
///
public enum QuestState
{
+ ///
+ /// Represents a quest / quest entry that has not been started yet.
+ ///
Inactive,
+
+ ///
+ /// Represents a quest / quest entry that has been started but not ended.
+ ///
Active,
+
+ ///
+ /// Represents a quest / quest entry that has been completed successfully by the player.
+ ///
Completed,
+
+ ///
+ /// Represents a quest / quest entry that has been failed by the played.
+ ///
Failed,
+
+ ///
+ /// Represents a quest / quest entry that has been expired.
+ ///
Expired,
+
+ ///
+ /// Represents a quest / quest entry that has been cancelled.
+ ///
Cancelled
}
}
\ No newline at end of file
diff --git a/S1API/S1API.cs b/S1API/S1API.cs
index b50ec326..76da5219 100644
--- a/S1API/S1API.cs
+++ b/S1API/S1API.cs
@@ -1,10 +1,46 @@
-using MelonLoader;
+#if (MONOMELON || IL2CPPMELON)
+using MelonLoader;
[assembly: MelonInfo(typeof(S1API.S1API), "S1API", "{VERSION_NUMBER}", "KaBooMa")]
namespace S1API
{
+ ///
+ /// Not currently utilized by S1API.
+ ///
public class S1API : MelonMod
{
}
}
+#elif (IL2CPPBEPINEX || MONOBEPINEX)
+using BepInEx;
+
+#if MONOBEPINEX
+using BepInEx.Unity.Mono;
+#elif IL2CPPBEPINEX
+using BepInEx.Unity.IL2CPP;
+#endif
+
+using HarmonyLib;
+
+namespace S1API
+{
+ [BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)]
+ public class S1API :
+#if MONOBEPINEX
+ BaseUnityPlugin
+#elif IL2CPPBEPINEX
+ BasePlugin
+#endif
+ {
+#if MONOBEPINEX
+ private void Awake()
+#elif IL2CPPBEPINEX
+ public override void Load()
+#endif
+ {
+ new Harmony("com.S1API").PatchAll();
+ }
+ }
+}
+#endif
diff --git a/S1API/S1API.csproj b/S1API/S1API.csproj
index e154ad15..fc6e8191 100644
--- a/S1API/S1API.csproj
+++ b/S1API/S1API.csproj
@@ -1,120 +1,108 @@
-
+
-
+
- netstandard2.1
KaBooMa
S1API
A Schedule One Mono / Il2Cpp Cross Compatibility Layer
https://github.com/KaBooMa/S1API
MIT
enable
- Mono;Il2Cpp
+ MonoMelon;MonoBepInEx;Il2CppMelon;Il2CppBepInEx
AnyCPU
S1API
+ true
+
+
+
+ netstandard2.1
+
+
+
+ net6.0
+
+
+
+
+ https://api.nuget.org/v3/index.json;
+ https://nuget.bepinex.dev/v3/index.json;
+ https://nuget.samboy.dev/v3/index.json
+
-
-
- $(MelonLoaderAssembliesPath)\Il2CppInterop.Runtime.dll
-
-
- $(MelonLoaderAssembliesPath)\Il2CppInterop.Common.dll
-
-
- $(MelonLoaderAssembliesPath)\Il2CppInterop.HarmonySupport.dll
-
-
- $(MelonLoaderAssembliesPath)\Il2CppInterop.Generator.dll
-
-
- $(Il2CppAssembliesPath)\Assembly-CSharp.dll
-
-
- $(Il2CppAssembliesPath)\Il2CppFishNet.Runtime.dll
-
-
- $(Il2CppAssembliesPath)\Il2Cppmscorlib.dll
-
-
- $(Il2CppAssembliesPath)\UnityEngine.dll
-
-
- $(Il2CppAssembliesPath)\UnityEngine.CoreModule.dll
-
-
- $(Il2CppAssembliesPath)\UnityEngine.UI.dll
-
-
- $(Il2CppAssembliesPath)\UnityEngine.UIModule.dll
-
-
- $(Il2CppAssembliesPath)\UnityEngine.JSONSerializeModule.dll
-
-
- $(Il2CppAssembliesPath)\UnityEngine.TextRenderingModule.dll
-
-
- $(Il2CppAssembliesPath)\UnityEngine.ImageConversionModule.dll
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
- $(MonoAssembliesPath)\Assembly-CSharp.dll
-
-
- $(MonoAssembliesPath)\UnityEngine.dll
-
-
- $(MonoAssembliesPath)\UnityEngine.CoreModule.dll
-
-
- $(MonoAssembliesPath)\FishNet.Runtime.dll
-
-
- $(MonoAssembliesPath)\UnityEngine.UI.dll
-
-
- $(MonoAssembliesPath)\UnityEngine.UIModule.dll
-
-
- $(MonoAssembliesPath)\UnityEngine.JSONSerializeModule.dll
-
-
- $(MonoAssembliesPath)\UnityEngine.TextRenderingModule.dll
-
-
- $(MonoAssembliesPath)\UnityEngine.ImageConversionModule.dll
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- $(MelonLoaderAssembliesPath)\0Harmony.dll
-
-
-
+
-
-
+
+
-
+
-
-
+
+
-
\ No newline at end of file
+
diff --git a/S1API/Saveables/SaveableField.cs b/S1API/Saveables/SaveableField.cs
index ec5be204..1df462af 100644
--- a/S1API/Saveables/SaveableField.cs
+++ b/S1API/Saveables/SaveableField.cs
@@ -4,6 +4,8 @@ namespace S1API.Saveables
{
///
/// Marks a field to be saved alongside the class instance.
+ /// This attribute is intended to work across all custom game elements.
+ /// (For example, custom NPCs, quests, etc.)
///
[AttributeUsage(AttributeTargets.Field)]
public class SaveableField : Attribute
@@ -13,6 +15,10 @@ public class SaveableField : Attribute
///
internal string SaveName { get; }
+ ///
+ /// Base constructor for initializing a SaveableField.
+ ///
+ ///
public SaveableField(string saveName)
{
SaveName = saveName;
diff --git a/S1API/Storages/StorageInstance.cs b/S1API/Storages/StorageInstance.cs
index 217f49f1..46561db8 100644
--- a/S1API/Storages/StorageInstance.cs
+++ b/S1API/Storages/StorageInstance.cs
@@ -1,6 +1,6 @@
-#if (IL2CPP)
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using S1Storage = Il2CppScheduleOne.Storage;
-#elif (MONO)
+#elif (MONOMELON || MONOBEPINEX)
using S1Storage = ScheduleOne.Storage;
#endif
@@ -67,4 +67,4 @@ public event Action OnClosed
remove => EventHelper.RemoveListener(value, S1Storage.onClosed);
}
}
-}
\ No newline at end of file
+}
diff --git a/S1API/UI/UIFactory.cs b/S1API/UI/UIFactory.cs
index 40fbb71f..faac087e 100644
--- a/S1API/UI/UIFactory.cs
+++ b/S1API/UI/UIFactory.cs
@@ -1,17 +1,13 @@
-#if (IL2CPPMELON || IL2CPPBEPINEX)
-
-using UnityEngine;
-using UnityEngine.UI;
+#if (IL2CPPMELON || IL2CPPBEPINEX)
using Il2CppInterop.Runtime.InteropTypes.Arrays;
-#else
-using UnityEngine;
-using UnityEngine.UI;
#endif
using System;
+using S1API.Internal.Utils;
+using UnityEngine;
using UnityEngine.Events;
+using UnityEngine.UI;
using System.Collections.Generic;
-
using Object = UnityEngine.Object;
namespace S1API.UI
@@ -382,33 +378,15 @@ public static void CreateRowButton(GameObject go, UnityAction clickHandler, bool
/// The transform whose child objects will be destroyed.
public static void ClearChildren(Transform parent)
{
- if (parent == null)
- {
- return;
- }
-
- try
- {
- int count = parent.childCount;
- for (int i = count - 1; i >= 0; i--)
- {
- var child = parent.GetChild(i);
- if (child != null)
- Object.Destroy(child.gameObject);
- }
-
- }
- catch (System.Exception e)
- {
- return;
- }
+ foreach (Transform child in parent)
+ GameObject.Destroy(child.gameObject);
}
/// Configures a GameObject to use a VerticalLayoutGroup with specified spacing and padding.
/// The GameObject to which a VerticalLayoutGroup will be added or configured.
/// The spacing between child objects within the VerticalLayoutGroup. Default is 10.
/// The padding around the edges of the VerticalLayoutGroup. If null, a default RectOffset of (10, 10, 10, 10) will be used.
- public static void VerticalLayoutOnGO(GameObject go, int spacing = 10, RectOffset padding = null)
+ public static void VerticalLayoutOnGO(GameObject go, int spacing = 10, RectOffset? padding = null)
{
var layout = go.AddComponent();
layout.spacing = spacing;
@@ -580,4 +558,4 @@ public void OnClick()
{
_callback.Invoke();
}
- }
+ }
\ No newline at end of file
diff --git a/S1APILoader/S1APILoader.cs b/S1APILoader/S1APILoader.cs
index 4c53f247..d52334d7 100644
--- a/S1APILoader/S1APILoader.cs
+++ b/S1APILoader/S1APILoader.cs
@@ -17,20 +17,22 @@ public override void OnPreModsLoaded()
if (pluginsFolder == null)
throw new Exception("Failed to identify plugins folder.");
- string buildsFolder = Path.Combine(pluginsFolder, BuildFolderName);
+ string modsFolder = Path.Combine(pluginsFolder, "../Mods");
string activeBuild = MelonUtils.IsGameIl2Cpp() ? "Il2Cpp" : "Mono";
+ string inactiveBuild = !MelonUtils.IsGameIl2Cpp() ? "Il2Cpp" : "Mono";
+
MelonLogger.Msg($"Loading S1API for {activeBuild}...");
- string s1APIBuildFile = Path.Combine(buildsFolder, $"S1API.{activeBuild}.dll");
+ string s1APIActiveBuildFile = Path.Combine(modsFolder, $"S1API.{activeBuild}.MelonLoader.dll");
+ string s1APIInactiveBuildFile = Path.Combine(modsFolder, $"S1API.{inactiveBuild}.MelonLoader.dll");
- // FIX: https://github.com/KaBooMa/S1API/issues/30
- // Manual assembly loading versus file manipulation.
- // Thunderstore doesn't pick it up if we do file manipulation.
- Assembly assembly = Assembly.LoadFile(s1APIBuildFile);
- MelonAssembly melonAssembly = MelonAssembly.LoadMelonAssembly(s1APIBuildFile, assembly);
- foreach (MelonBase melon in melonAssembly.LoadedMelons)
- melon.Register();
+ string disabledActiveBuildFile = $"{s1APIActiveBuildFile}.disabled";
+ if (File.Exists(disabledActiveBuildFile))
+ File.Move($"{s1APIActiveBuildFile}.disabled", s1APIActiveBuildFile);
+
+ if (File.Exists(s1APIInactiveBuildFile))
+ File.Move(s1APIInactiveBuildFile, $"{s1APIInactiveBuildFile}.disabled");
MelonLogger.Msg($"Successfully loaded S1API for {activeBuild}!");
}
diff --git a/S1APILoader/S1APILoader.csproj b/S1APILoader/S1APILoader.csproj
index 8d79d7be..b664e6a4 100644
--- a/S1APILoader/S1APILoader.csproj
+++ b/S1APILoader/S1APILoader.csproj
@@ -6,9 +6,9 @@
- netstandard2.1
+ netstandard2.1;net6.0
enable
- Mono;Il2Cpp
+ Il2CppMelon;MonoBepInEx;MonoMelon;Il2CppBepInEx
AnyCPU
S1APILoader
@@ -20,13 +20,13 @@
-
+
-
+
-
+
-
+
diff --git a/example.build.props b/example.build.props
index 3c0a2143..475f048a 100644
--- a/example.build.props
+++ b/example.build.props
@@ -11,6 +11,7 @@
C:\Program Files (x86)\Steam\steamapps\common\Schedule I\MelonLoader\net6
+ C:\Program Files (x86)\Steam\steamapps\common\Schedule I\BepInEx\core
@@ -28,4 +29,4 @@
C:\Program Files (x86)\Steam\steamapps\common\Schedule I\Melonloader\Il2CppAssemblies
-
\ No newline at end of file
+
diff --git a/github.build.props b/github.build.props
index 1648fc4d..2c4c39ec 100644
--- a/github.build.props
+++ b/github.build.props
@@ -11,6 +11,7 @@
..\ScheduleOneAssemblies\MelonLoader
+ ..\ScheduleOneAssemblies\BepInEx