diff --git a/S1API/Dialogues/DialogueChoiceListener.cs b/S1API/Dialogues/DialogueChoiceListener.cs new file mode 100644 index 00000000..4e46c679 --- /dev/null +++ b/S1API/Dialogues/DialogueChoiceListener.cs @@ -0,0 +1,67 @@ +using System; +using UnityEngine.Events; +#if IL2CPPBEPINEX || IL2CPPMELON +using Il2CppScheduleOne.Dialogue; +#elif MONOBEPINEX || MONOMELON +using ScheduleOne.Dialogue; +#endif + +namespace S1API.Dialogues +{ + /// + /// A static utility class that listens for and responds to specific dialogue choices in the game's dialogue system. + /// + public static class DialogueChoiceListener + { + /// + /// Stores the label of the expected dialogue choice that, when selected, + /// triggers the associated callback action in the dialogue system. + /// + /// + /// This variable is utilized internally by the DialogueChoiceListener class + /// to match the label of the choice selected by the user. When the label matches + /// expectedChoiceLabel, the registered callback is executed. + /// + private static string expectedChoiceLabel; + + /// + /// Represents a delegate invoked when a specific dialogue choice is selected during interaction. + /// + private static Action callback; + + /// Registers a specific dialogue choice with a callback to be invoked when the choice is selected. + /// The reference to the DialogueHandler that manages dialogue choices. + /// The label identifying the specific dialogue choice to be registered. + /// The callback action to execute when the dialogue choice is selected. + public static void Register(DialogueHandler handlerRef, string label, Action action) + { + expectedChoiceLabel = label; + callback = action; + + if (handlerRef != null) + { + void ForwardCall() => OnChoice(); + + // ✅ IL2CPP-safe: explicit method binding via wrapper + handlerRef.onDialogueChoiceChosen.AddListener((UnityAction)delegate (string choice) + { + if (choice == expectedChoiceLabel) + ((UnityAction)ForwardCall).Invoke(); + }); + } + } + + /// + /// Executes the registered callback when the expected dialogue choice is selected. + /// + /// + /// This method is invoked internally and should not be called directly. + /// It ensures that the provided callback is executed only when the expected dialogue choice matches. + /// + private static void OnChoice() + { + callback?.Invoke(); + callback = null; // optional: remove if one-time use + } + } +} \ No newline at end of file diff --git a/S1API/Dialogues/DialogueInjection.cs b/S1API/Dialogues/DialogueInjection.cs new file mode 100644 index 00000000..40208322 --- /dev/null +++ b/S1API/Dialogues/DialogueInjection.cs @@ -0,0 +1,75 @@ +using System; + +/// +/// Represents a dialogue injection configuration for adding custom dialogues into an NPC's conversation flow dynamically. +/// +public class DialogueInjection +{ + /// + /// Represents the name of the NPC (Non-Player Character) to which the dialogue injection is associated. + /// This value is expected to match or partially match the name of an NPC in the game, allowing the system + /// to identify and target the specific NPC for dialogue modifications. + /// + public string NpcName; + + /// + /// Represents the name of the dialogue container being referenced for injections or modifications + /// within the NPC's dialogue system. + /// + /// + /// This variable is used for identifying a specific dialogue container when attempting to + /// inject new dialogue nodes, choices, or links into an NPC's dialogue setup. + /// + public string ContainerName; + + /// + /// Represents the unique identifier (GUID) of the starting dialogue node within a dialogue container. + /// + /// + /// This variable is used to identify the specific dialogue node from which a new choice or interaction is injected. + /// + public string FromNodeGuid; + + /// + /// Represents the unique identifier (GUID) for the target dialogue node to which a choice or link is pointing in a dialogue system. + /// + public string ToNodeGuid; + + /// + /// Represents a descriptive label for a dialogue choice used in the dialogue system. + /// + /// + /// This label is utilized for identifying a specific dialogue choice during execution + /// and for associating a callback or specific functionality when that choice is selected. + /// + public string ChoiceLabel; + + /// + /// Represents the text displayed for a dialogue choice in the game's dialogue system. + /// + /// + /// The property is utilized to define the text that appears visually for a specific dialogue choice + /// in conjunction with the dialogue system. The text is injected dynamically during runtime for scenarios + /// requiring additional or modified dialogue options. + /// + public string ChoiceText; + + /// + /// Represents a callback action that is invoked when a dialogue choice is confirmed. + /// + public Action OnConfirmed; + + /// + /// Represents an injectable dialogue configuration that can be used to add or modify dialogue interactions in a game. + /// + public DialogueInjection(string npc, string container, string from, string to, string label, string text, Action onConfirmed) + { + NpcName = npc; + ContainerName = container; + FromNodeGuid = from; + ToNodeGuid = to; + ChoiceLabel = label; + ChoiceText = text; + OnConfirmed = onConfirmed; + } +} \ No newline at end of file diff --git a/S1API/Dialogues/DialogueInjector.cs b/S1API/Dialogues/DialogueInjector.cs new file mode 100644 index 00000000..85b8aee2 --- /dev/null +++ b/S1API/Dialogues/DialogueInjector.cs @@ -0,0 +1,163 @@ +using System.Collections.Generic; + +using UnityEngine; +using MelonLoader; +using S1API.Dialogues; +#if IL2CPPMELON || IL2CPPBEPINEX +using Il2CppScheduleOne.Dialogue; +using Il2CppScheduleOne.NPCs; +using Il2CppScheduleOne.NPCs.Schedules; +#else +using ScheduleOne.Dialogue; +using ScheduleOne.NPCs; +using ScheduleOne.NPCs.Schedules; +#endif +/// +/// The DialogueInjector class is a static utility that facilitates the injection of custom dialogue entries +/// into a game's dialogue system at runtime. It provides methods for registering custom dialogue injections +/// and ensures that these injections are processed correctly within the update loop. +/// +public static class DialogueInjector +{ + /// + /// Represents a collection of dialogue injections waiting to be processed for corresponding NPCs in the game. + /// This variable stores pending dialogue link or choice modifications that need to be applied to NPC dialogue systems + /// and is used during a coroutine process to find the relevant NPC and complete the injection. + /// + private static List pendingInjections = new List(); + + /// + /// A boolean variable that indicates whether the update loop for injecting dialogue is currently hooked. + /// When true, the update loop has been hooked and is actively monitoring for pending dialogue injections; otherwise, it has not been hooked. + /// + private static bool isHooked = false; + + /// + /// Registers a dialogue injection to be processed in the update loop. + /// + /// An instance of representing the dialogue to be injected into the game. + public static void Register(DialogueInjection injection) + { + pendingInjections.Add(injection); + HookUpdateLoop(); + } + + /// + /// Ensures that the dialogue injection system's loop is hooked into the game's update cycle. + /// This method starts a coroutine to monitor and inject pending dialogue changes into NPCs. + /// + /// + /// This method prevents multiple hookups by checking if the injection system is already active using an internal flag. + /// If not already hooked, the method initializes a coroutine that processes and injects queued dialogue data into the corresponding NPCs. + /// + private static void HookUpdateLoop() + { + if (isHooked) return; + isHooked = true; + + MelonCoroutines.Start(WaitForNPCsAndInject()); + } + + /// + /// Monitors the current state of NPCs within the game world and manages the injection of dialogue + /// into the appropriate NPCs once they are found. This method waits for instances of NPC objects + /// that match the specified criteria in the pending dialogue injections and processes these injections + /// once a match is located. The method continues execution until all pending dialogue injections + /// have been completed. + /// + /// + /// An enumerator that handles the coroutine execution for periodic checks and dialogue injection processing. + /// + private static System.Collections.IEnumerator WaitForNPCsAndInject() + { + while (pendingInjections.Count > 0) + { + for (int i = pendingInjections.Count - 1; i >= 0; i--) + { + var injection = pendingInjections[i]; + var npcs = GameObject.FindObjectsOfType(); + NPC target = null; + + for (int j = 0; j < npcs.Length; j++) + { + if (npcs[j] != null && npcs[j].name.Contains(injection.NpcName)) + { + target = npcs[j]; + break; + } + } + + if (target != null) + { + TryInject(injection, target); + pendingInjections.RemoveAt(i); + } + } + + yield return null; // Wait one frame + } + } + + /// + /// Attempts to inject a dialogue choice and link into the specified NPC's dialogue system. + /// + /// The dialogue injection object containing the data for the choice to inject. + /// The NPC that will have the dialogue choice injected. + private static void TryInject(DialogueInjection injection, NPC npc) + { + var handler = npc.GetComponent(); + var dialogueEvent = npc.GetComponentInChildren(true); + if (dialogueEvent == null || dialogueEvent.DialogueOverride == null) return; + + if (dialogueEvent.DialogueOverride.name != injection.ContainerName) return; + + var container = dialogueEvent.DialogueOverride; + if (container.DialogueNodeData == null) return; + + DialogueNodeData node = null; + for (int i = 0; i < container.DialogueNodeData.Count; i++) + { + var n = container.DialogueNodeData[i]; + if (n != null && n.Guid == injection.FromNodeGuid) + { + node = n; + break; + } + } + + if (node == null) return; + + var choice = new DialogueChoiceData + { + Guid = System.Guid.NewGuid().ToString(), + ChoiceLabel = injection.ChoiceLabel, + ChoiceText = injection.ChoiceText + }; + + var choiceList = new List(); + if (node.choices != null) + choiceList.AddRange(node.choices); + + choiceList.Add(choice); + node.choices = choiceList.ToArray(); + + var link = new NodeLinkData + { + BaseDialogueOrBranchNodeGuid = injection.FromNodeGuid, + BaseChoiceOrOptionGUID = choice.Guid, + TargetNodeGuid = injection.ToNodeGuid + }; + + if (container.NodeLinks == null) + #if IL2CPPMELON || IL2CPPBEPINEX + container.NodeLinks = new Il2CppSystem.Collections.Generic.List(); +#else + container.NodeLinks = new List(); +#endif + container.NodeLinks.Add(link); + + DialogueChoiceListener.Register(handler, injection.ChoiceLabel, injection.OnConfirmed); + + MelonLogger.Msg($"[DialogueInjector] Injected '{injection.ChoiceLabel}' into NPC '{npc.name}'"); + } +} \ No newline at end of file diff --git a/S1API/Property/BaseProperty.cs b/S1API/Property/BaseProperty.cs new file mode 100644 index 00000000..8a1075f3 --- /dev/null +++ b/S1API/Property/BaseProperty.cs @@ -0,0 +1,69 @@ +namespace S1API.Property +{ + /// + /// Represents an abstract base class for properties in the system. + /// + public abstract class BaseProperty + { + /// + /// Gets the name of the property. + /// + /// + /// This property represents the name or title of the property entity. + /// It retrieves the value associated with the property from the underlying system or data structure. + /// + public abstract string PropertyName { get; } + + /// + /// Gets the unique code that identifies the property. + /// + /// + /// This code is typically used to differentiate between various properties + /// within the system. It is unique to each property and can be leveraged + /// in operations like identification, filtering, or querying. + /// + public abstract string PropertyCode { get; } + + /// + /// Represents the cost or monetary value associated with a property. + /// + /// + /// The Price property is a floating-point value that indicates the price for the property. + /// It provides a read-only mechanism to access this value. + /// This value is essential in determining the economic aspect of the property. + /// + /// + /// A float representing the monetary price of the property. + /// + public abstract float Price { get; } + + /// Indicates whether the property is currently owned or not. + /// This property is read-only and reflects the ownership status + /// of the property. + public abstract bool IsOwned { get; } + + /// + /// Gets or sets the maximum number of employees that can be assigned to the property. + /// + /// + /// This property represents the capacity for employees within a given property. + /// Modifying this value impacts the operations and resource management of the property. + /// Suitable for scenarios where resource allocation and workforce planning are essential. + /// + public abstract int EmployeeCapacity { get; set; } + + /// + /// Marks the property as owned. This method updates the ownership status + /// of the property by interacting with the underlying property implementation. + /// Typically used to signify that the property has been acquired or purchased. + /// + public abstract void SetOwned(); + + /// + /// Determines whether a specified point lies within the boundary of the property. + /// + /// The point to check, specified as a Vector3 coordinate. + /// true if the point is within the property's boundary; otherwise, false. + public abstract bool IsPointInside(UnityEngine.Vector3 point); + } +} \ No newline at end of file diff --git a/S1API/Property/PropertyManager.cs b/S1API/Property/PropertyManager.cs new file mode 100644 index 00000000..48bcb166 --- /dev/null +++ b/S1API/Property/PropertyManager.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +#if IL2CPPMELON || IL2CPPBEPINEX +using Il2CppScheduleOne.Property; +#elif MONOMELON || MONOBEPINEX +using ScheduleOne.Property; +#endif +namespace S1API.Property +{ + /// + /// Provides methods for managing and retrieving property data within the application. + /// + public static class PropertyManager + { + /// + /// Retrieves a list of all properties available. + /// + /// A list of objects representing all available properties. + public static List GetAllProperties() + { + var list = new List(); + #if IL2CPPMELON || IL2CPPBEPINEX + foreach (var prop in Il2CppScheduleOne.Property.Property.Properties) +#else + foreach (var prop in ScheduleOne.Property.Property.Properties) + #endif + { + list.Add(new PropertyWrapper(prop)); + } + return list; + } + + /// Retrieves a list of all properties that are currently owned. + /// + /// A list of PropertyWrapper objects representing the owned properties. + /// + public static List GetOwnedProperties() + { + var list = new List(); +#if IL2CPPMELON || IL2CPPBEPINEX + foreach (var prop in Il2CppScheduleOne.Property.Property.OwnedProperties) +#elif MONOMELON || MONOBEPINEX + foreach (var prop in ScheduleOne.Property.Property.OwnedProperties) +#endif + { + list.Add(new PropertyWrapper(prop)); + } + return list; + } + + /// + /// Finds a property with the given name from the list of available properties. + /// + /// The name of the property to search for. + /// + /// A representing the property with the specified name if found; otherwise, null. + /// + public static PropertyWrapper FindPropertyByName(string name) + { +#if IL2CPPMELON || IL2CPPBEPINEX + foreach (var prop in Il2CppScheduleOne.Property.Property.Properties) +#elif MONOMELON || MONOBEPINEX + foreach (var prop in ScheduleOne.Property.Property.Properties) +#endif + { + if (prop.PropertyName == name) + { + return new PropertyWrapper(prop); + } + } + return null; + } + } +} \ No newline at end of file diff --git a/S1API/Property/PropertyWrapper.cs b/S1API/Property/PropertyWrapper.cs new file mode 100644 index 00000000..fa4d180f --- /dev/null +++ b/S1API/Property/PropertyWrapper.cs @@ -0,0 +1,104 @@ +using UnityEngine; + +namespace S1API.Property +{ + /// + /// Represents a wrapper class for handling properties derived from the + /// Il2CppScheduleOne.Property.Property class. Provides an abstraction for + /// interacting with property details and operations in Unity. + /// + public class PropertyWrapper : BaseProperty + { + /// + /// A readonly backing field encapsulating the core property instance + /// used within the PropertyWrapper class. This field provides access + /// to the underlying implementation of the property functionalities + /// and is leveraged across multiple overriden members to delegate + /// operations to the actual property instance. + /// + #if IL2CPPBEPINEX || IL2CPPMELON + internal readonly Il2CppScheduleOne.Property.Property InnerProperty; + #else + internal readonly ScheduleOne.Property.Property InnerProperty; + #endif + /// + /// A wrapper class that extends the functionality of + /// and acts as a bridge to interact with an inner property implementation + /// from the Il2CppScheduleOne.Property namespace. + /// +#if IL2CPPBEPINEX || IL2CPPMELON + public PropertyWrapper(Il2CppScheduleOne.Property.Property property) + + #else + public PropertyWrapper(ScheduleOne.Property.Property property) +#endif + { + InnerProperty = property; + } + + /// + /// Gets the name of the property. + /// Represents the underlying property name as defined by its implementation. + /// + public override string PropertyName => InnerProperty.PropertyName; + + /// + /// Gets the unique code representing this property. This code serves as an identifier + /// for distinguishing the property in the system and is typically defined in the internal + /// implementation of the property. + /// + public override string PropertyCode => InnerProperty.PropertyCode; + + /// + /// Gets the price of the property. + /// + /// + /// The price represents a floating-point value that denotes the monetary + /// value or cost associated with the property. This property is read-only + /// and retrieves the value from the underlying property implementation. + /// + public override float Price => InnerProperty.Price; + + /// + /// Gets a value indicating whether the property is currently owned. + /// + /// + /// This property reflects the ownership status of the property. Returns true if the property + /// is owned and false otherwise. The ownership status is based on the internal state of the + /// wrapped property implementation. + /// + public override bool IsOwned => InnerProperty.IsOwned; + + /// + /// Represents the maximum number of employees that can be allocated to the property. + /// This property is both readable and writable, allowing for dynamic configuration + /// of employee capacity based on the property's current requirements or constraints. + /// + public override int EmployeeCapacity + { + get => InnerProperty.EmployeeCapacity; + set => InnerProperty.EmployeeCapacity = value; + } + + /// + /// Marks the property as owned within the PropertyWrapper implementation. + /// Updates the ownership status by delegating the operation to the underlying + /// Il2CppScheduleOne.Property.Property instance. + /// This is typically used to signify that the property has been acquired or purchased. + /// + public override void SetOwned() + { + InnerProperty.SetOwned(); + } + + /// + /// Determines whether a specified point lies within the boundary of the property. + /// + /// The point to check, specified as a Vector3 coordinate. + /// true if the point is within the property's boundary; otherwise, false. + public override bool IsPointInside(Vector3 point) + { + return InnerProperty.DoBoundsContainPoint(point); + } + } +} \ No newline at end of file