Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions S1API/Dialogues/DialogueChoiceListener.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// A static utility class that listens for and responds to specific dialogue choices in the game's dialogue system.
/// </summary>
public static class DialogueChoiceListener
{
/// <summary>
/// Stores the label of the expected dialogue choice that, when selected,
/// triggers the associated callback action in the dialogue system.
/// </summary>
/// <remarks>
/// This variable is utilized internally by the <c>DialogueChoiceListener</c> class
/// to match the label of the choice selected by the user. When the label matches
/// <c>expectedChoiceLabel</c>, the registered callback is executed.
/// </remarks>
private static string expectedChoiceLabel;

Check warning on line 25 in S1API/Dialogues/DialogueChoiceListener.cs

View workflow job for this annotation

GitHub Actions / Verify Successful Build

Non-nullable field 'expectedChoiceLabel' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 25 in S1API/Dialogues/DialogueChoiceListener.cs

View workflow job for this annotation

GitHub Actions / Verify Successful Build

Non-nullable field 'expectedChoiceLabel' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

/// <summary>
/// Represents a delegate invoked when a specific dialogue choice is selected during interaction.
/// </summary>
private static Action callback;

Check warning on line 30 in S1API/Dialogues/DialogueChoiceListener.cs

View workflow job for this annotation

GitHub Actions / Verify Successful Build

Non-nullable field 'callback' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 30 in S1API/Dialogues/DialogueChoiceListener.cs

View workflow job for this annotation

GitHub Actions / Verify Successful Build

Non-nullable field 'callback' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

/// Registers a specific dialogue choice with a callback to be invoked when the choice is selected.
/// <param name="handlerRef">The reference to the DialogueHandler that manages dialogue choices.</param>
/// <param name="label">The label identifying the specific dialogue choice to be registered.</param>
/// <param name="action">The callback action to execute when the dialogue choice is selected.</param>
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<string>)delegate (string choice)
{
if (choice == expectedChoiceLabel)
((UnityAction)ForwardCall).Invoke();
});
}
}

/// <summary>
/// Executes the registered callback when the expected dialogue choice is selected.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
private static void OnChoice()
{
callback?.Invoke();
callback = null; // optional: remove if one-time use

Check warning on line 64 in S1API/Dialogues/DialogueChoiceListener.cs

View workflow job for this annotation

GitHub Actions / Verify Successful Build

Cannot convert null literal to non-nullable reference type.

Check warning on line 64 in S1API/Dialogues/DialogueChoiceListener.cs

View workflow job for this annotation

GitHub Actions / Verify Successful Build

Cannot convert null literal to non-nullable reference type.
}
}
}
75 changes: 75 additions & 0 deletions S1API/Dialogues/DialogueInjection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;

/// <summary>
/// Represents a dialogue injection configuration for adding custom dialogues into an NPC's conversation flow dynamically.
/// </summary>
public class DialogueInjection
{
/// <summary>
/// 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.
/// </summary>
public string NpcName;

/// <summary>
/// Represents the name of the dialogue container being referenced for injections or modifications
/// within the NPC's dialogue system.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public string ContainerName;

/// <summary>
/// Represents the unique identifier (GUID) of the starting dialogue node within a dialogue container.
/// </summary>
/// <remarks>
/// This variable is used to identify the specific dialogue node from which a new choice or interaction is injected.
/// </remarks>
public string FromNodeGuid;

/// <summary>
/// Represents the unique identifier (GUID) for the target dialogue node to which a choice or link is pointing in a dialogue system.
/// </summary>
public string ToNodeGuid;

/// <summary>
/// Represents a descriptive label for a dialogue choice used in the dialogue system.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public string ChoiceLabel;

/// <summary>
/// Represents the text displayed for a dialogue choice in the game's dialogue system.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public string ChoiceText;

/// <summary>
/// Represents a callback action that is invoked when a dialogue choice is confirmed.
/// </summary>
public Action OnConfirmed;

/// <summary>
/// Represents an injectable dialogue configuration that can be used to add or modify dialogue interactions in a game.
/// </summary>
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;
}
}
163 changes: 163 additions & 0 deletions S1API/Dialogues/DialogueInjector.cs
Original file line number Diff line number Diff line change
@@ -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
/// <summary>
/// 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.
/// </summary>
public static class DialogueInjector
{
/// <summary>
/// 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.
/// </summary>
private static List<DialogueInjection> pendingInjections = new List<DialogueInjection>();

/// <summary>
/// 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.
/// </summary>
private static bool isHooked = false;

/// <summary>
/// Registers a dialogue injection to be processed in the update loop.
/// </summary>
/// <param name="injection">An instance of <see cref="DialogueInjection"/> representing the dialogue to be injected into the game.</param>
public static void Register(DialogueInjection injection)
{
pendingInjections.Add(injection);
HookUpdateLoop();
}

/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
private static void HookUpdateLoop()
{
if (isHooked) return;
isHooked = true;

MelonCoroutines.Start(WaitForNPCsAndInject());
}

/// <summary>
/// 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.
/// </summary>
/// <returns>
/// An enumerator that handles the coroutine execution for periodic checks and dialogue injection processing.
/// </returns>
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>();
NPC target = null;

Check warning on line 79 in S1API/Dialogues/DialogueInjector.cs

View workflow job for this annotation

GitHub Actions / Verify Successful Build

Converting null literal or possible null value to non-nullable type.

Check warning on line 79 in S1API/Dialogues/DialogueInjector.cs

View workflow job for this annotation

GitHub Actions / Verify Successful Build

Converting null literal or possible null value to non-nullable type.

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
}
}

/// <summary>
/// Attempts to inject a dialogue choice and link into the specified NPC's dialogue system.
/// </summary>
/// <param name="injection">The dialogue injection object containing the data for the choice to inject.</param>
/// <param name="npc">The NPC that will have the dialogue choice injected.</param>
private static void TryInject(DialogueInjection injection, NPC npc)
{
var handler = npc.GetComponent<DialogueHandler>();
var dialogueEvent = npc.GetComponentInChildren<NPCEvent_LocationDialogue>(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;

Check warning on line 117 in S1API/Dialogues/DialogueInjector.cs

View workflow job for this annotation

GitHub Actions / Verify Successful Build

Converting null literal or possible null value to non-nullable type.

Check warning on line 117 in S1API/Dialogues/DialogueInjector.cs

View workflow job for this annotation

GitHub Actions / Verify Successful Build

Converting null literal or possible null value to non-nullable type.
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<DialogueChoiceData>();
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<NodeLinkData>();
#else
container.NodeLinks = new List<NodeLinkData>();
#endif
container.NodeLinks.Add(link);

DialogueChoiceListener.Register(handler, injection.ChoiceLabel, injection.OnConfirmed);

MelonLogger.Msg($"[DialogueInjector] Injected '{injection.ChoiceLabel}' into NPC '{npc.name}'");
}
}
69 changes: 69 additions & 0 deletions S1API/Property/BaseProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace S1API.Property
{
/// <summary>
/// Represents an abstract base class for properties in the system.
/// </summary>
public abstract class BaseProperty
{
/// <summary>
/// Gets the name of the property.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public abstract string PropertyName { get; }

/// <summary>
/// Gets the unique code that identifies the property.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public abstract string PropertyCode { get; }

/// <summary>
/// Represents the cost or monetary value associated with a property.
/// </summary>
/// <remarks>
/// The <c>Price</c> 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.
/// </remarks>
/// <value>
/// A <c>float</c> representing the monetary price of the property.
/// </value>
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; }

/// <summary>
/// Gets or sets the maximum number of employees that can be assigned to the property.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public abstract int EmployeeCapacity { get; set; }

/// <summary>
/// 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.
/// </summary>
public abstract void SetOwned();

/// <summary>
/// Determines whether a specified point lies within the boundary of the property.
/// </summary>
/// <param name="point">The point to check, specified as a Vector3 coordinate.</param>
/// <returns>true if the point is within the property's boundary; otherwise, false.</returns>
public abstract bool IsPointInside(UnityEngine.Vector3 point);
}
}
Loading
Loading