using Godot;
using Godot.Collections;
using System;
namespace DialogueManagerRuntime
{
///
/// A special node to hold information about starting dialogue.
///
/// Assuming DialogueResource and DialogueCue have been configured you can
/// call Action() on this node at runtime to start dialogue.
///
[Tool, Icon("uid://dppsuhlvbcbjc")]
public partial class DialogueActionable3D : Area3D
{
///
/// Emitted when this has Action() called on it.
///
[Signal] public delegate void ActionedEventHandler();
///
/// Emitted when the dialogue resource associated with this ends. NOTE: The
/// signal is also emitted if the same resource is used for multiple nodes in the tree.
///
[Signal] public delegate void DialogueEndedEventHandler();
private Resource dialogueResource = null;
///
/// The dialogue resource to use when starting dialogue.
///
[Export]
public Resource DialogueResource
{
get => dialogueResource;
set
{
dialogueResource = value;
if (dialogueResource == null)
{
DialogueCue = "";
}
NotifyPropertyListChanged();
}
}
///
/// The target cue to start dialogue from.
///
[Export] public string DialogueCue = "";
///
/// The dialogue balloon that was last used by calling Action() (if there was one).
///
public Node DialogueBalloon;
///
/// The method used to start dialogue when Action() is called. Override if you need different logic.
///
public static Func, 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
///
/// Action this . If a dialogue resource and cue have been set on this node then it will start dialogue.
///
public void Action()
{
if (IsInstanceValid(DialogueResource) && !string.IsNullOrEmpty(DialogueCue))
{
DialogueBalloon = StartDialogue(DialogueResource, DialogueCue, new Array { new Dictionary { { "actionable", this } }, Owner });
}
EmitSignal(SignalName.Actioned);
}
///
/// Find the nearest to a given position.
///
public static DialogueActionable3D GetNearestActionableTo(Vector3 targetPosition)
{
float nearestDistance = float.PositiveInfinity;
DialogueActionable3D nearestActionable = null;
foreach (Node node in ((SceneTree)Engine.GetMainLoop()).GetNodesInGroup("dialogue_actionables"))
{
if (node is not DialogueActionable3D 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
}
}