Data/Dialogue.cs
using System;
using System.Threading.Tasks;
using Clover.Components;
using Clover.Inventory;
using Clover.Persistence;
using Clover.Player;
using Clover.Ui;
using Clover.WorldBuilder;
using Sandbox.Diagnostics;
using Sandbox.UI;
namespace Clover.Data;
[AssetType( Name = "Dialogue", Extension = "dlg" )]
public class Dialogue : GameResource
{
/*public class DialogueAction
{
[Property, ActionGraphInclude] public GameObject Player { get; set; }
[Property] public List<GameObject> Targets { get; set; }
[Property] public DialogueNode Node { get; set; }
[Property] public DialogueChoice Choice { get; set; }
}*/
public DialogueAction Tree { get; set; } = null;
[ActionGraphNode( "clover.dialogue.textnode" )]
[Title( "Dialogue Text Node" ), Group( "Dialogue" ), Icon( "chat" )]
public static async Task DialogueTextNode( GameObject speaker, [TextArea] string text )
{
Log.Info( "New DialogueTextNode" );
if ( DialogueManager.Instance.DialogueWindow.DialogueNodeCompletedTaskSource != null )
{
Log.Error( "DialogueTextNode: DialogueNodeCompletedTaskSource is not null." );
return;
}
DialogueManager.Instance.DialogueWindow.Enabled = true;
DialogueManager.Instance.DialogueWindow.ChoicesPanel?.DeleteChildren();
DialogueManager.Instance.DialogueWindow.DispatchText( speaker, text );
DialogueManager.Instance.DialogueWindow.IsCurrentNodeChoice = false;
Log.Info( "Creating new dialogue node completed task source." );
DialogueManager.Instance.DialogueWindow.DialogueNodeCompletedTaskSource = new TaskCompletionSource();
Log.Info( "Waiting for dialogue node to complete." );
await DialogueManager.Instance.DialogueWindow.DialogueNodeCompletedTaskSource.Task;
Log.Info( "Dialogue node completed." );
DialogueManager.Instance.DialogueWindow.DialogueNodeCompletedTaskSource = null;
}
[ActionGraphNode( "clover.dialogue.choicenode" )]
[Title( "Dialogue Choice Node" ), Group( "Dialogue" ), Icon( "chat" )]
public static async Task DialogueChoiceNode( GameObject speaker, [TextArea] string text, string[] choices,
Action onChoose0, Action onChoose1, Action onChoose2, Action onChoose3, Action onChoose4, Action onChoose5 )
{
if ( choices.Length == 0 )
{
Log.Error( "DialogueChoiceNode: No choices provided." );
return;
}
if ( choices.Length > 6 )
{
Log.Error( "DialogueChoiceNode: Too many choices provided." );
return;
}
Log.Info( "DialogueChoiceNode" );
DialogueManager.Instance.DialogueWindow.Enabled = true;
DialogueManager.Instance.DialogueWindow.DispatchText( speaker, text );
DialogueManager.Instance.DialogueWindow.IsCurrentNodeChoice = true;
await Task.Delay( 100 );
if ( !DialogueManager.Instance.DialogueWindow.ChoicesPanel.IsValid() )
{
Log.Error( "DialogueChoiceNode: ChoicesPanel is null." );
return;
}
DialogueManager.Instance.DialogueWindow.ChoicesPanel.DeleteChildren();
int index = 0;
foreach ( var choice in choices )
{
if ( string.IsNullOrEmpty( choice ) )
{
Log.Error( "DialogueChoiceNode: Empty choice provided." );
return;
}
var _index = index;
var button = new Button( choice );
button.AddEventListener( "onclick", ( PanelEvent e ) =>
{
switch ( _index )
{
case 0:
onChoose0();
break;
case 1:
onChoose1();
break;
case 2:
onChoose2();
break;
case 3:
onChoose3();
break;
case 4:
onChoose4();
break;
case 5:
onChoose5();
break;
}
e.StopPropagation();
} );
DialogueManager.Instance.DialogueWindow.ChoicesPanel.AddChild( button );
index++;
}
}
[ActionGraphNode( "clover.dialogue.itemselectnode" )]
[Title( "Item Select Node" ), Group( "Dialogue" ), Icon( "chat" )]
public static async Task<int[]> ItemSelectNode( int maxItems )
{
Log.Info( "ItemSelectNode" );
if ( DialogueManager.Instance.DialogueWindow.ChoicesPanel.IsValid() )
{
DialogueManager.Instance.DialogueWindow.ChoicesPanel.DeleteChildren();
}
var result = await MainUi.Instance.Components.Get<InventorySelectUi>( true )
.SelectItems( maxItems, ( item ) => item.GetItem().ItemData.CanSell );
Log.Info( "ItemSelectNode: Selected items" );
return result.ToArray();
}
[ActionGraphNode( "clover.dialogue.inventoryindexestoslots" )]
[Title( "Inventory Indexes To Slots" ), Group( "Dialogue" ), Icon( "chat" )]
public static List<InventorySlot> InventoryIndexesToSlots( InventoryContainer container,
int[] indexes )
{
return indexes.Select( container.GetSlotByIndex ).Where( slot => slot != null ).ToList();
}
[ActionGraphNode( "clover.dialogue.end" )]
[Title( "End Dialogue" ), Group( "Dialogue" ), Icon( "chat" )]
public static void EndDialogue()
{
Log.Info( "EndDialogue" );
DialogueManager.Instance.DialogueWindow.End();
}
[ActionGraphNode( "clover.dialogue.setdata" )]
[Title( "Set Dialogue Data" ), Group( "Dialogue" ), Icon( "chat" )]
public static void SetData( string key, object value )
{
Log.Info( $"SetData: {key} = {value}" );
DialogueManager.Instance.DialogueWindow.SetData( key, value );
}
[ActionGraphNode( "clover.dialogue.getdatageneric" )]
[Title( "Get Dialogue Data" ), Group( "Dialogue" ), Icon( "chat" )]
public static T GetData<T>( string key )
{
Log.Info( $"GetData: {key}" );
return DialogueManager.Instance.DialogueWindow.GetData<T>( key );
}
[ActionGraphNode( "clover.dialogue.getitemssellprice.itemdata" )]
[Title( "Get Items Sell Price (ItemData)" ), Group( "Dialogue" ), Icon( "chat" )]
public static int GetItemsSellPrice( List<ItemData> items )
{
return items.Sum( item => item.GetCustomSellPrice?.Invoke( TimeManager.Time ) ?? item.BaseSellPrice );
}
[ActionGraphNode( "clover.dialogue.getitemssellprice.slot" )]
[Title( "Get Items Sell Price (Slot)" ), Group( "Dialogue" ), Icon( "chat" )]
public static int GetItemsSellPrice( List<InventorySlot> slots )
{
return slots.Sum( slot =>
slot.GetItem().ItemData.GetCustomSellPrice?.Invoke( TimeManager.Time ) ??
slot.GetItem().ItemData.BaseSellPrice );
}
/*[ActionGraphNode( "clover.dialogue.quickcompare" )]
public static Task<bool> QuickCompare( int a, int b )
{
if ( a == b )
{
return Task.FromResult( true );
}
else
{
return Task.FromResult( false );
}
}*/
public delegate void DialogueAction(
DialogueWindow window,
PlayerCharacter player,
List<GameObject> targets,
DialogueNode node,
DialogueChoice choice
);
public delegate bool DialogueCondition(
DialogueWindow window,
PlayerCharacter player,
List<GameObject> targets,
DialogueNode node,
DialogueChoice choice
);
public class DialogueChoice
{
[Property] public string Label { get; set; } = null;
// [Property] public string Id { get; set; }
// [Property] public string IdTarget { get; set; }
[Property] public DialogueAction OnSelect { get; set; } = null;
[Property] public string JumpToId { get; set; } = null;
[Property, Description( "Will only show if OnSelect is null" )]
public List<DialogueNode> Nodes { get; set; } = new();
public override string ToString()
{
if ( Nodes.Count == 0 && !string.IsNullOrEmpty( JumpToId ) )
{
return $"{Label} -> JUMP:{JumpToId}";
}
else if ( Nodes.Count == 0 && OnSelect != null )
{
return $"{Label} -> ACTION";
}
return $"{Label} -> {Nodes.Count} nodes";
}
}
public class DialogueNode
{
[Property] public string Id { get; set; } = Guid.NewGuid().ToString()[..8];
[Property, Description( "I don't think the player ever talks, but this is here just in case." )]
public bool IsPlayer { get; set; } = false;
[Property, HideIf( nameof(IsPlayer), true )]
public int Speaker { get; set; } = 0;
[Property, TextArea] public string Text { get; set; } = null;
// [Property] public List<string> Choices { get; set; } = new();
[Property] public List<DialogueChoice> Choices { get; set; } = new();
[Property, Description( "This is the action that will be executed when this node is entered." )]
public DialogueAction OnEnter { get; set; } = null;
[Property] public DialogueAction OnExit { get; set; } = null;
/// <summary>
/// If true, this node will not be advanced to automatically, and must be jumped to manually.
/// </summary>
[Property]
public bool IsHidden { get; set; } = false;
public override string ToString()
{
if ( !string.IsNullOrEmpty( Id ) )
{
return $"#{Id} - {Speaker}: {Text}";
}
return $"{Speaker}: {Text}";
}
}
[Property, InlineEditor] public List<DialogueNode> Nodes { get; set; } = new();
}