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