Files

142 lines
4.6 KiB
C#

using Godot;
using Godot.Collections;
using System;
namespace DialogueManagerRuntime
{
/// <summary>
/// A special <see cref="Area2D"/> node to hold information about starting dialogue.
///
/// Assuming <c>DialogueResource</c> and <c>DialogueCue</c> have been configured you can
/// call <c>Action()</c> on this node at runtime to start dialogue.
/// </summary>
[Tool, Icon("uid://ja67jst5iqyy")]
public partial class DialogueActionable2D : Area2D
{
/// <summary>
/// Emitted when this <see cref="DialogueActionable2D"/> has <c>Action()</c> called on it.
/// </summary>
[Signal] public delegate void ActionedEventHandler();
/// <summary>
/// Emitted when the dialogue resource associated with this <see cref="DialogueActionable2D"/> ends. NOTE: The
/// signal is also emitted if the same resource is used for multiple <see cref="DialogueActionable2D"/> nodes in the tree.
/// </summary>
[Signal] public delegate void DialogueEndedEventHandler();
private Resource dialogueResource = null;
/// <summary>
/// The dialogue resource to use when starting dialogue.
/// </summary>
[Export]
public Resource DialogueResource
{
get => dialogueResource;
set
{
dialogueResource = value;
if (dialogueResource == null)
{
DialogueCue = "";
}
NotifyPropertyListChanged();
}
}
/// <summary>
/// The target cue to start dialogue from.
/// </summary>
[Export] public string DialogueCue = "";
/// <summary>
/// The dialogue balloon that was last used by calling <c>Action()</c> (if there was one).
/// </summary>
public Node DialogueBalloon;
/// <summary>
/// The method used to start dialogue when <c>Action()</c> is called. Override if you need different logic.
/// </summary>
public static Func<Resource, string, Array<Variant>, Node> StartDialogue = (withDialogueResource, fromCue, extraGameStates) =>
DialogueManager.ShowDialogueBalloon(withDialogueResource, fromCue, extraGameStates);
public override void _Ready()
{
if (!Engine.IsEditorHint())
{
AddToGroup("dialogue_actionables");
// Touch the instance to make sure the DialogueManager bridge signals are connected
_ = DialogueManager.Instance;
DialogueManager.DialogueEnded += OnDialogueEnded;
}
}
public override void _ExitTree()
{
if (!Engine.IsEditorHint())
{
DialogueManager.DialogueEnded -= OnDialogueEnded;
}
}
#region Public
/// <summary>
/// Action this <see cref="DialogueActionable2D"/>. If a dialogue resource and cue have been set on this node then it will start dialogue.
/// </summary>
public void Action()
{
if (IsInstanceValid(DialogueResource) && !string.IsNullOrEmpty(DialogueCue))
{
DialogueBalloon = StartDialogue(DialogueResource, DialogueCue, new Array<Variant> { new Dictionary { { "actionable", this } }, Owner });
}
EmitSignal(SignalName.Actioned);
}
/// <summary>
/// Find the nearest <see cref="DialogueActionable2D"/> to a given position.
/// </summary>
public static DialogueActionable2D GetNearestActionableTo(Vector2 targetPosition)
{
float nearestDistance = float.PositiveInfinity;
DialogueActionable2D nearestActionable = null;
foreach (Node node in ((SceneTree)Engine.GetMainLoop()).GetNodesInGroup("dialogue_actionables"))
{
if (node is not DialogueActionable2D actionable) continue;
float distance = actionable.GlobalPosition.DistanceSquaredTo(targetPosition);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestActionable = actionable;
}
}
return nearestActionable;
}
#endregion
#region Signals
private void OnDialogueEnded(Resource endingDialogueResource)
{
if (endingDialogueResource == DialogueResource)
{
EmitSignal(SignalName.DialogueEnded);
}
}
#endregion
}
}