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 callback) + { + if (Internal != null) + Internal.onNetworthCalculation += callback; + } + + /// + /// Removes a previously registered networth calculation callback. + /// + /// The callback to be removed from the networth calculation updates. + public static void RemoveNetworthCalculation(System.Action callback) + { + if (Internal != null) + Internal.onNetworthCalculation -= callback; + } + + /// + /// Creates a new cash instance with the specified balance. + /// + /// The initial amount of cash to set in the instance. + /// A newly created instance of cash with the specified balance. + public static CashInstance CreateCashInstance(float amount) + { +#if (IL2CPPMELON || IL2CPPBEPINEX) + var cashItem = Registry.GetItem("cash"); + var instance = CrossType.As(cashItem.GetDefaultInstance(1)); + var cashInstance = new CashInstance(instance); + cashInstance.SetQuantity(amount); + return cashInstance; +#else + var cashItem = Registry.GetItem("cash"); + var instance = CrossType.As(cashItem.GetDefaultInstance(1)); + var cashInstance = new CashInstance(instance); + cashInstance.SetQuantity(amount); + return cashInstance; + +#endif + } + } +} diff --git a/S1API/NPCs/NPC.cs b/S1API/NPCs/NPC.cs deleted file mode 100644 index a9a14699..00000000 --- a/S1API/NPCs/NPC.cs +++ /dev/null @@ -1,270 +0,0 @@ -#if (IL2CPP) -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 (MONO) -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; -using System.Reflection; -using HarmonyLib; -#endif - -using System; -using System.IO; -using S1API.Internal.Abstraction; -using UnityEngine; -using UnityEngine.Events; - -namespace S1API.NPCs -{ - /// - /// An abstract class intended to be derived from for creating custom NPCs in the game. - /// - public abstract class NPC : Saveable - { - /// - /// A list of text responses you've added to your NPC. - /// - protected readonly System.Collections.Generic.List Responses = - new System.Collections.Generic.List(); - - internal readonly S1NPCs.NPC S1NPC; - - private readonly GameObject _gameObject; - /// - /// Optional custom mugshot icon sprite. If null, defaults to ContactsApp icon. - /// protected override Sprite? NPCIcon => ImageUtils.LoadImage("icon.png"); - /// - protected virtual Sprite? NPCIcon => null; - - /// - /// Base constructor for a new NPC. - /// Intended to be wrapped in your derived class constructor such as: - /// public class MyNPC : NPC ... - /// public MyNPC() : base(id, fname, lname) { ... } - /// - /// The unique identifier for this NPC - /// - /// - public NPC(string guid, string firstName, string lastName) - { - _gameObject = new GameObject("NPC"); - - // Deactivate game object til we're done - _gameObject.SetActive(false); - - // Setup the base NPC class - S1NPC = _gameObject.AddComponent(); - S1NPC.FirstName = firstName; - S1NPC.LastName = lastName; - S1NPC.ID = guid; - S1NPC.BakedGUID = Guid.NewGuid().ToString(); - S1NPC.MugshotSprite = NPCIcon ?? S1DevUtilities.PlayerSingleton.Instance.AppIcon; - - // ReSharper disable once UseObjectOrCollectionInitializer - S1NPC.ConversationCategories = new List(); - S1NPC.ConversationCategories.Add(S1Messaging.EConversationCategory.Customer); - - // Create our MessageConversation -#if (IL2CPP) - S1NPC.CreateMessageConversation(); -#elif (MONO) - 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.SetActive(false); - 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.SetActive(false); - 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.SetActive(false); - responsesObject.transform.SetParent(_gameObject.transform); - S1NPC.awareness.Responses = responsesObject.AddComponent(); - - // Vision cone object and behaviour - GameObject visionObject = new GameObject("VisionCone"); - visionObject.SetActive(false); - 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 (IL2CPP) - S1NPC.intObj = _gameObject.AddComponent(); -#elif (MONO) - FieldInfo intObjField = AccessTools.Field(typeof(S1NPCs.NPC), "intObj"); - intObjField.SetValue(S1NPC, _gameObject.AddComponent()); -#endif - - // Relationship data - S1NPC.RelationData = new S1Relation.NPCRelationData(); - - // void OnUnlockAction(S1Relation.NPCRelationData.EUnlockType unlockType, bool notify) - // { - // if (!string.IsNullOrEmpty(S1NPC.NPCUnlockedVariable)) - // { - // S1DevUtilities.NetworkSingleton.Instance.SetVariableValue(S1NPC.NPCUnlockedVariable, true.ToString()); - // } - // } - - // S1NPC.RelationData.onUnlocked += (Action)OnUnlockAction; - - // 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; - - - // NetworkObject networkObject = gameObject.AddComponent(); - // // networkObject.NetworkBehaviours = InstanceFinder.NetworkManager; - // PropertyInfo networkBehavioursProperty = AccessTools.Property(typeof(NetworkObject), "NetworkBehaviours"); - // networkBehavioursProperty.SetValue(networkObject, new [] { this }); - - // Enable our custom gameObjects so they can initialize - _gameObject.SetActive(true); - visionObject.SetActive(true); - responsesObject.SetActive(true); - awarenessObject.SetActive(true); - behaviourObject.SetActive(true); - } - - /// - /// 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); - } - - /// - /// 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 - ); - } - - /// - /// 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) { } - } -} \ No newline at end of file diff --git a/S1API/NPCs/NPCInstance.cs b/S1API/NPCs/NPCInstance.cs deleted file mode 100644 index d4858d1b..00000000 --- a/S1API/NPCs/NPCInstance.cs +++ /dev/null @@ -1,13 +0,0 @@ -#if (IL2CPP) -using S1NPCs = Il2CppScheduleOne.NPCs; -#elif (MONO) -using S1NPCs = ScheduleOne.NPCs; -#endif - -namespace S1API.NPCs -{ - public class NPCInstance - { - - } -} \ No newline at end of file diff --git a/S1API/NPCs/NPCManager.cs b/S1API/NPCs/NPCManager.cs deleted file mode 100644 index b8fa86dc..00000000 --- a/S1API/NPCs/NPCManager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace S1API.NPCs -{ - public class NPCManager - { - - } -} \ No newline at end of file diff --git a/S1API/PhoneApp/MyAwesomeApp.cs b/S1API/PhoneApp/MyAwesomeApp.cs index 54ef4495..31d3c30f 100644 --- a/S1API/PhoneApp/MyAwesomeApp.cs +++ b/S1API/PhoneApp/MyAwesomeApp.cs @@ -5,13 +5,15 @@ namespace S1API.PhoneApp { - /// - /// Defines the MyAwesomeApp, a specialized application integrated into an in-game phone system. - /// - /// - /// This class leverages the PhoneApp framework to specify application-specific properties like name, title, - /// icon label, and icon file name. It also overrides the method for defining the user interface layout upon creation. - /// + // TODO(@omar-akermi): Can we move this into markdown pls <3 + + // /// + // /// Defines the MyAwesomeApp, a specialized application integrated into an in-game phone system. + // /// + // /// + // /// This class leverages the PhoneApp framework to specify application-specific properties like name, title, + // /// icon label, and icon file name. It also overrides the method for defining the user interface layout upon creation. + // /// /* public class MyAwesomeApp : PhoneApp { diff --git a/S1API/PhoneApp/PhoneApp.cs b/S1API/PhoneApp/PhoneApp.cs index 44385db8..f0b426fd 100644 --- a/S1API/PhoneApp/PhoneApp.cs +++ b/S1API/PhoneApp/PhoneApp.cs @@ -1,143 +1,174 @@ -#if IL2CPP +using System.IO; using UnityEngine; using UnityEngine.UI; -using UnityEngine.Events; -#elif MONO -using UnityEngine; -using UnityEngine.UI; -using UnityEngine.Events; -#endif - -using System.Collections; -using System.IO; -using MelonLoader; using Object = UnityEngine.Object; -using MelonLoader.Utils; using S1API.Internal.Abstraction; - +using S1API.Internal.Patches; +#if IL2CPPMELON +using Il2CppScheduleOne.UI.Phone; +using MelonLoader.Utils; +#elif IL2CPPBEPINEX +using Il2CppScheduleOne.UI.Phone; +#elif MONOBEPINEX +using ScheduleOne.UI.Phone; +#elif MONOMELON +using ScheduleOne.UI.Phone; +using MelonLoader.Utils; +#endif namespace S1API.PhoneApp { /// - /// Serves as an abstract base class for creating in-game phone applications with customizable functionality and appearance. + /// Abstract base class for creating custom applications to be used within an in-game phone system. /// /// - /// Implementations of this class enable the creation of bespoke applications that integrate seamlessly into a game's phone system. - /// Derived classes are required to define key properties and methods, such as application name, icon, and UI behavior. - /// This class also manages the app's lifecycle, including its initialization and destruction processes. + /// This class provides an extensible framework for defining application behaviors, user interface elements, + /// and registration mechanics for integration into the phone's ecosystem. /// public abstract class PhoneApp : Registerable { - protected static readonly MelonLogger.Instance LoggerInstance = new MelonLogger.Instance("PhoneApp"); - /// - /// The player object in the scene. + /// Logger instance used for logging messages, warnings, or errors + /// related to the functionality of in-game phone applications. /// - protected GameObject Player; + protected static readonly Logging.Log Logger = new Logging.Log("PhoneApp"); /// - /// The in-game UI panel representing the app. + /// Represents the panel associated with the phone app's UI. + /// This is dynamically instantiated or retrieved when the app is initiated and serves as the container + /// for the app's user interface elements within the phone system. The panel exists within the + /// app canvas structure in the game's Unity hierarchy. /// - protected GameObject AppPanel; + private GameObject? _appPanel; /// - /// Whether the app was successfully created. + /// Indicates whether the application UI has been successfully created and initialized. /// - protected bool AppCreated; + /// + /// This variable is used internally to track the state of the application's UI. + /// When set to true, it denotes that the app UI panel has been created and configured. + /// + private bool _appCreated; /// - /// Whether the app icon was already modified. + /// Indicates whether the phone application icon has been modified. + /// This flag prevents redundant modification of the icon once it has already + /// been updated or created. /// - protected bool IconModified; + private bool _iconModified; /// - /// Unique GameObject name of the app (e.g. "SilkroadApp"). + /// Gets the unique identifier for the application within the phone system. /// + /// + /// This property is used as a key to identify the application when creating UI elements or interacting with other components + /// of the in-game phone system. It must be implemented in derived classes to provide a consistent and unique name for + /// the application. + /// protected abstract string AppName { get; } /// - /// The title shown at the top of the app UI. + /// Gets the display title of the application as it appears in the in-game phone system. /// + /// + /// This property specifies the human-readable name of the application, different from the internal + /// AppName that uniquely identifies the app within the system. It is displayed to the user + /// on the application icon or within the application UI. + /// protected abstract string AppTitle { get; } /// - /// The label shown under the app icon on the home screen. + /// Gets the label text displayed on the application's icon. /// + /// + /// The IconLabel property is an abstract member that must be overridden by each implementation + /// of the class. It specifies the label text shown directly below the application's + /// icon on the in-game phone's home screen. + /// This property is utilized when creating or modifying the app's icon, as part of the SpawnIcon method, + /// to ensure that the label represents the application's name or a relevant description. The value should + /// be concise and contextually meaningful to the user. + /// + /// + /// A string representing the label text displayed under the app icon, which explains or identifies + /// the app to the user. + /// protected abstract string IconLabel { get; } /// - /// The PNG file name (in UserData) used for the app icon. + /// Specifies the file name of the icon used to represent the phone application in the in-game phone system. /// + /// + /// The value of this property is typically a string containing the file name of the icon asset, + /// such as "icon-name.png". It is used to identify and load the appropriate icon for the application. + /// protected abstract string IconFileName { get; } /// - /// Called when the app's UI should be created inside the container. + /// Invoked to define the user interface layout when the application panel is created. + /// The method is used to populate the provided container with custom UI elements specific to the application. /// - /// The container GameObject to build into. + /// The GameObject container where the application's UI elements will be added. protected abstract void OnCreatedUI(GameObject container); /// - /// Called when the app is loaded into the scene (delayed after phone UI is present). + /// Invoked when the PhoneApp instance is created. + /// Responsible for registering the app with the PhoneAppRegistry, + /// integrating it into the in-game phone system. /// protected override void OnCreated() { - MelonCoroutines.Start(InitApp()); + PhoneAppRegistry.Register(this); } /// - /// Called when the app is unloaded or the scene is reset. + /// Cleans up resources and resets state when the app is destroyed. + /// This method ensures any associated UI elements and resources are properly disposed of and variables tracking the app state are reset. /// protected override void OnDestroyed() { - if (AppPanel != null) + if (_appPanel != null) { - Object.Destroy(AppPanel); - AppPanel = null; + Object.Destroy(_appPanel); + _appPanel = null; } - AppCreated = false; - IconModified = false; + _appCreated = false; + _iconModified = false; } /// - /// Coroutine that injects the app UI and icon after scene/UI has loaded. + /// Generates and initializes the UI panel for the application within the in-game phone system. + /// This method locates the parent container in the UI hierarchy, clones a template panel if needed, + /// clears its content, and then invokes the implementation-specific OnCreatedUI method + /// for further customization of the UI panel. /// - private IEnumerator InitApp() + internal void SpawnUI(HomeScreen homeScreenInstance) { - yield return new WaitForSeconds(5f); - - Player = GameObject.Find("Player_Local"); - if (Player == null) - { - LoggerInstance.Error("Player_Local not found."); - yield break; - } - - GameObject appsCanvas = GameObject.Find("Player_Local/CameraContainer/Camera/OverlayCamera/GameplayMenu/Phone/phone/AppsCanvas"); + GameObject? appsCanvas = homeScreenInstance.transform.parent.Find("AppsCanvas")?.gameObject; if (appsCanvas == null) { - LoggerInstance.Error("AppsCanvas not found."); - yield break; + Logger.Error("AppsCanvas not found."); + return; } Transform existingApp = appsCanvas.transform.Find(AppName); if (existingApp != null) { - AppPanel = existingApp.gameObject; - SetupExistingAppPanel(AppPanel); + _appPanel = existingApp.gameObject; + SetupExistingAppPanel(_appPanel); } else { Transform templateApp = appsCanvas.transform.Find("ProductManagerApp"); if (templateApp == null) { - LoggerInstance.Error("Template ProductManagerApp not found."); - yield break; + Logger.Error("Template ProductManagerApp not found."); + return; } - AppPanel = Object.Instantiate(templateApp.gameObject, appsCanvas.transform); - AppPanel.name = AppName; + _appPanel = Object.Instantiate(templateApp.gameObject, appsCanvas.transform); + _appPanel.name = AppName; - Transform containerTransform = AppPanel.transform.Find("Container"); + Transform containerTransform = _appPanel.transform.Find("Container"); if (containerTransform != null) { GameObject container = containerTransform.gameObject; @@ -145,21 +176,54 @@ private IEnumerator InitApp() OnCreatedUI(container); } - AppCreated = true; + _appCreated = true; } - AppPanel.SetActive(true); + _appPanel.SetActive(true); + } - if (!IconModified) + /// + /// Creates or modifies the application icon displayed on the in-game phone's home screen. + /// This method clones an existing icon, updates its label, and changes its image based on the provided file name. + /// + internal void SpawnIcon(HomeScreen homeScreenInstance) + { + if (_iconModified) + return; + + GameObject? appIcons = homeScreenInstance.transform.Find("AppIcons")?.gameObject; + if (appIcons == null) { - IconModified = ModifyAppIcon(IconLabel, IconFileName); + Logger.Error("AppIcons not found under HomeScreen."); + return; } + + // Find the LAST icon (the one most recently added) + Transform? lastIcon = appIcons.transform.childCount > 0 ? appIcons.transform.GetChild(appIcons.transform.childCount - 1) : null; + if (lastIcon == null) + { + Logger.Error("No icons found in AppIcons."); + return; + } + + GameObject iconObj = lastIcon.gameObject; + iconObj.name = AppName; // Rename it now + + // Update label + Transform labelTransform = iconObj.transform.Find("Label"); + Text? label = labelTransform?.GetComponent(); + if (label != null) + label.text = IconLabel; + + // Update image + _iconModified = ChangeAppIconImage(iconObj, IconFileName); } + /// - /// Configures the provided GameObject panel to prepare it for use with the app. + /// Configures an existing app panel by clearing and rebuilding its UI elements if necessary. /// - /// The GameObject representing the UI panel of the app. + /// The app panel to configure, represented as a GameObject. private void SetupExistingAppPanel(GameObject panel) { Transform containerTransform = panel.transform.Find("Container"); @@ -173,9 +237,13 @@ private void SetupExistingAppPanel(GameObject panel) } } - AppCreated = true; + _appCreated = true; } + /// + /// Removes all child objects from the specified container to clear its contents. + /// + /// The parent GameObject whose child objects will be destroyed. private void ClearContainer(GameObject container) { for (int i = container.transform.childCount - 1; i >= 0; i--) @@ -183,62 +251,32 @@ private void ClearContainer(GameObject container) } /// - /// Modifies the application's icon by cloning an existing icon, updating its label, - /// and setting a new icon image based on the specified parameters. + /// Changes the image of the app icon based on the specified filename, and applies the new icon to the given GameObject. /// - /// The text to be displayed as the label for the modified icon. - /// The file name of the new icon image to apply. + /// The GameObject representing the app icon that will have its image changed. + /// The name of the file containing the new icon image to be loaded. /// - /// A boolean value indicating whether the icon modification was successful. - /// Returns true if the modification was completed successfully; otherwise, false. + /// A boolean value indicating whether the operation was successful. + /// Returns true if the image was successfully loaded and applied; otherwise, returns false. /// - private bool ModifyAppIcon(string labelText, string fileName) - { - GameObject parent = GameObject.Find("Player_Local/CameraContainer/Camera/OverlayCamera/GameplayMenu/Phone/phone/HomeScreen/AppIcons/"); - if (parent == null) - { - LoggerInstance?.Error("AppIcons not found."); - return false; - } - - Transform lastIcon = parent.transform.childCount > 0 ? parent.transform.GetChild(parent.transform.childCount - 1) : null; - if (lastIcon == null) - { - LoggerInstance?.Error("No icon found to clone."); - return false; - } - - GameObject iconObj = lastIcon.gameObject; - iconObj.name = AppName; - - Transform labelTransform = iconObj.transform.Find("Label"); - Text label = labelTransform?.GetComponent(); - if (label != null) label.text = labelText; - - return ChangeAppIconImage(iconObj, fileName); - } - - - /// - /// Updates the app icon image with the specified file if the corresponding Image component is found and the file exists. - /// - /// The GameObject representing the app icon whose image is to be updated. - /// The name of the image file to be loaded and applied as the icon. - /// True if the icon image was successfully updated, otherwise false. private bool ChangeAppIconImage(GameObject iconObj, string filename) { Transform imageTransform = iconObj.transform.Find("Mask/Image"); - Image image = imageTransform?.GetComponent(); + Image? image = imageTransform?.GetComponent(); if (image == null) { - LoggerInstance?.Error("Image component not found in icon."); + Logger.Error("Image component not found in icon."); return false; } +#if MONOMELON || IL2CPPMELON string path = Path.Combine(MelonEnvironment.ModsDirectory, filename); +#elif MONOBEPINEX || IL2CPPBEPINEX + string path = Path.Combine(BepInEx.Paths.PluginPath, filename); +#endif if (!File.Exists(path)) { - LoggerInstance?.Error("Icon file not found: " + path); + Logger.Error("Icon file not found: " + path); return false; } @@ -255,7 +293,7 @@ private bool ChangeAppIconImage(GameObject iconObj, string filename) } catch (System.Exception e) { - LoggerInstance?.Error("Failed to load image: " + e.Message); + Logger.Error("Failed to load image: " + e.Message); } return false; diff --git a/S1API/Products/CocaineDefinition.cs b/S1API/Products/CocaineDefinition.cs index e0b45e3c..5507e6f8 100644 --- a/S1API/Products/CocaineDefinition.cs +++ b/S1API/Products/CocaineDefinition.cs @@ -1,7 +1,7 @@ -#if IL2CPP +#if (IL2CPPMELON || IL2CPPBEPINEX) using Il2CppScheduleOne.Product; using S1CocaineDefinition = Il2CppScheduleOne.Product.CocaineDefinition; -#elif MONO +#elif (MONOMELON || MONOBEPINEX) using ScheduleOne.Product; using S1CocaineDefinition = ScheduleOne.Product.CocaineDefinition; #endif @@ -37,4 +37,4 @@ public override ItemInstance CreateInstance(int quantity = 1) => new ProductInstance(CrossType.As( S1CocaineDefinition.GetDefaultInstance(quantity))); } -} \ No newline at end of file +} diff --git a/S1API/Products/MethDefinition.cs b/S1API/Products/MethDefinition.cs index 943f5d72..8904a616 100644 --- a/S1API/Products/MethDefinition.cs +++ b/S1API/Products/MethDefinition.cs @@ -1,7 +1,7 @@ -#if IL2CPP +#if (IL2CPPMELON || IL2CPPBEPINEX) using Il2CppScheduleOne.Product; using S1MethDefinition = Il2CppScheduleOne.Product.MethDefinition; -#elif MONO +#elif (MONOMELON || MONOBEPINEX) using ScheduleOne.Product; using S1MethDefinition = ScheduleOne.Product.MethDefinition; #endif @@ -36,4 +36,4 @@ public override ItemInstance CreateInstance(int quantity = 1) => new ProductInstance(CrossType.As( S1MethDefinition.GetDefaultInstance(quantity))); } -} \ No newline at end of file +} diff --git a/S1API/Products/PackagingDefinition.cs b/S1API/Products/PackagingDefinition.cs index b9dfee8c..54052293 100644 --- a/S1API/Products/PackagingDefinition.cs +++ b/S1API/Products/PackagingDefinition.cs @@ -1,7 +1,7 @@ -#if (IL2CPP) +#if (IL2CPPMELON || IL2CPPBEPINEX) using S1Packaging = Il2CppScheduleOne.Product.Packaging; using S1ItemFramework = Il2CppScheduleOne.ItemFramework; -#elif (MONO) +#elif (MONOMELON || MONOBEPINEX) using S1Packaging = ScheduleOne.Product.Packaging; using S1ItemFramework = ScheduleOne.ItemFramework; #endif @@ -35,4 +35,4 @@ internal PackagingDefinition(S1ItemFramework.ItemDefinition s1ItemDefinition) : public int Quantity => S1PackagingDefinition.Quantity; } -} \ No newline at end of file +} diff --git a/S1API/Products/ProductDefinition.cs b/S1API/Products/ProductDefinition.cs index bcb467a7..3c4b7fb2 100644 --- a/S1API/Products/ProductDefinition.cs +++ b/S1API/Products/ProductDefinition.cs @@ -1,8 +1,8 @@ -#if IL2CPP +#if (IL2CPPMELON || IL2CPPBEPINEX) using Il2CppInterop.Runtime.InteropTypes; using S1Product = Il2CppScheduleOne.Product; -#elif (MONO) +#elif (MONOMELON || MONOBEPINEX) using S1Product = ScheduleOne.Product; #endif @@ -52,4 +52,4 @@ public Sprite Icon } } -} \ No newline at end of file +} diff --git a/S1API/Products/ProductDefinitionWrapper.cs b/S1API/Products/ProductDefinitionWrapper.cs index 97903e3e..2670adca 100644 --- a/S1API/Products/ProductDefinitionWrapper.cs +++ b/S1API/Products/ProductDefinitionWrapper.cs @@ -1,5 +1,5 @@ using S1API.Internal.Utils; -#if IL2CPP +#if (IL2CPPMELON || IL2CPPBEPINEX) using S1Product = Il2CppScheduleOne.Product; #else using S1Product = ScheduleOne.Product; @@ -7,9 +7,12 @@ namespace S1API.Products { - public static class ProductDefinitionWrapper + /// + /// INTERNAL: A wrapper class for converting a product definition to its proper dedicated class. + /// + internal static class ProductDefinitionWrapper { - public static ProductDefinition Wrap(ProductDefinition def) + internal static ProductDefinition Wrap(ProductDefinition def) { var item = def.S1ItemDefinition; if (CrossType.Is(item, out var weed)) @@ -21,4 +24,4 @@ public static ProductDefinition Wrap(ProductDefinition def) return def; } } -} \ No newline at end of file +} diff --git a/S1API/Products/ProductInstance.cs b/S1API/Products/ProductInstance.cs index a1380bce..f3763101 100644 --- a/S1API/Products/ProductInstance.cs +++ b/S1API/Products/ProductInstance.cs @@ -1,6 +1,6 @@ -#if (IL2CPP) +#if (IL2CPPMELON || IL2CPPBEPINEX) using S1Product = Il2CppScheduleOne.Product; -#elif (MONO) +#elif (MONOMELON || MONOBEPINEX) using S1Product = ScheduleOne.Product; #endif @@ -38,4 +38,4 @@ internal ProductInstance(S1Product.ProductItemInstance productInstance) : base(p public PackagingDefinition AppliedPackaging => new PackagingDefinition(S1ProductInstance.AppliedPackaging); } -} \ No newline at end of file +} diff --git a/S1API/Products/ProductManager.cs b/S1API/Products/ProductManager.cs index 264d65ca..4c5625e1 100644 --- a/S1API/Products/ProductManager.cs +++ b/S1API/Products/ProductManager.cs @@ -1,7 +1,7 @@ -#if (IL2CPP) +#if (IL2CPPMELON || IL2CPPBEPINEX) using S1Product = Il2CppScheduleOne.Product; using Il2CppSystem.Collections.Generic; -#elif (MONO) +#elif (MONOMELON || MONOBEPINEX) using S1Product = ScheduleOne.Product; #endif using System.Linq; @@ -21,4 +21,4 @@ public static class ProductManager .ToArray(); } -} \ No newline at end of file +} diff --git a/S1API/Products/WeedDefinition.cs b/S1API/Products/WeedDefinition.cs index 6c953289..f467819e 100644 --- a/S1API/Products/WeedDefinition.cs +++ b/S1API/Products/WeedDefinition.cs @@ -1,7 +1,7 @@ -#if IL2CPP +#if (IL2CPPMELON || IL2CPPBEPINEX) using Il2CppScheduleOne.Product; using S1WeedDefinition = Il2CppScheduleOne.Product.WeedDefinition; -#elif MONO +#elif (MONOMELON || MONOBEPINEX) using ScheduleOne.Product; using S1WeedDefinition = ScheduleOne.Product.WeedDefinition; #endif @@ -37,4 +37,4 @@ public override ItemInstance CreateInstance(int quantity = 1) => new ProductInstance(CrossType.As( S1WeedDefinition.GetDefaultInstance(quantity))); } -} \ No newline at end of file +} diff --git a/S1API/Quests/Quest.cs b/S1API/Quests/Quest.cs index 4fdfdc88..f281bccb 100644 --- a/S1API/Quests/Quest.cs +++ b/S1API/Quests/Quest.cs @@ -1,11 +1,11 @@ -#if (IL2CPP) +#if (IL2CPPMELON || IL2CPPBEPINEX) using S1Quests = Il2CppScheduleOne.Quests; using S1Dev = Il2CppScheduleOne.DevUtilities; using S1Map = Il2CppScheduleOne.Map; using S1Data = Il2CppScheduleOne.Persistence.Datas; using S1Contacts = Il2CppScheduleOne.UI.Phone.ContactsApp; using Il2CppSystem.Collections.Generic; -#elif (MONO) +#elif (MONOMELON || MONOBEPINEX) using S1Quests = ScheduleOne.Quests; using S1Dev = ScheduleOne.DevUtilities; using S1Map = ScheduleOne.Map; @@ -87,10 +87,10 @@ public Quest() S1Quest.onTrackChange = new UnityEvent(); S1Quest.TrackOnBegin = true; S1Quest.AutoCompleteOnAllEntriesComplete = true; -#if (MONO) +#if (MONOMELON || MONOBEPINEX) FieldInfo autoInitField = AccessTools.Field(typeof(S1Quests.Quest), "autoInitialize"); autoInitField.SetValue(S1Quest, false); -#elif (IL2CPP) +#elif (IL2CPPMELON || IL2CPPBEPINEX) S1Quest.autoInitialize = false; #endif @@ -138,10 +138,10 @@ public Quest() poiPrefabObject.transform.SetParent(_gameObject.transform); S1Map.POI poi = poiPrefabObject.AddComponent(); poi.DefaultMainText = "Did it work?"; -#if (MONO) +#if (MONOMELON || MONOBEPINEX) FieldInfo uiPrefabField = AccessTools.Field(typeof(S1Map.POI), "UIPrefab"); uiPrefabField.SetValue(poi, uiPrefabObject); -#elif (IL2CPP) +#elif (IL2CPPMELON || IL2CPPBEPINEX) poi.UIPrefab = uiPrefabObject; #endif S1Quest.PoIPrefab = poiPrefabObject; @@ -241,4 +241,4 @@ protected QuestEntry AddEntry(string title, Vector3? poiPosition = null) /// 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