Ui/DialogueWindow.razor.cs
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Clover.Data;
using Clover.Npc;
using Clover.Player;
using Sandbox.UI;
namespace Clover;
public partial class DialogueWindow
{
public Panel ChoicesPanel;
[Property] public Dialogue Dialogue { get; set; }
[Property] public Dictionary<string, object> Data { get; set; } = new();
[Property] public Dictionary<string, Action> Actions { get; set; } = new();
[Property, ReadOnly] public List<Dialogue.DialogueNode> CurrentNodeList { get; set; }
// [Property, ReadOnly] public Dialogue.DialogueChoice CurrentChoice { get; set; }
[Property, ReadOnly] public List<GameObject> CurrentTargets { get; set; } = new();
public int CurrentNodeIndex;
public Dialogue.DialogueNode CurrentNode
{
get => CurrentNodeList.ElementAtOrDefault( CurrentNodeIndex );
// set => CurrentNodeIndex = Dialogue.Nodes.IndexOf( value );
}
public bool IsOnLastNode => CurrentNodeIndex == CurrentNodeList.Count - 1;
public string Text { get; set; } = "";
public string Name { get; set; } = "";
private int _textIndex;
private string _textTarget;
private TimeSince _lastLetter;
// private bool _skipped;
public bool IsCurrentNodeChoice;
public Action OnDialogueEnd { get; set; }
public TaskCompletionSource DialogueNodeCompletedTaskSource { get; set; }
protected override void OnStart()
{
base.OnStart();
/*Data = new Dictionary<string, object> { { "test", 123 }, { "money", 100 }, { "price", 200 }, };
CurrentNodeList = Dialogue.Nodes;
CurrentNodeIndex = 0;
CurrentNode.OnEnter?.Invoke( this, null, null, CurrentNode, null );
Read();*/
// LoadDialogue( ResourceLibrary.GetAll<Dialogue>().First() );
Panel.ButtonInput = PanelInputType.UI;
}
public void LoadDialogue( Dialogue dialogue )
{
/*Dialogue = dialogue;
CurrentNodeList = Dialogue.Nodes;
CurrentNodeIndex = 0;
CurrentNode.OnEnter?.Invoke( this, PlayerCharacter.Local, CurrentTargets, CurrentNode, null );
Read();*/
if ( dialogue == null )
{
Log.Error( "DialogueWindow LoadDialogue: No dialogue found" );
return;
}
Dialogue = dialogue;
if ( Dialogue.Tree == null )
{
Log.Error( "DialogueWindow LoadDialogue: No dialogue tree found" );
return;
}
Dialogue.Tree.Invoke( this, PlayerCharacter.Local, CurrentTargets, null, null );
}
[Pure]
[Icon( "description" )]
public int GetDataInt( string key )
{
if ( Data.TryGetValue( key, out var value ) )
{
if ( value is int i )
return i;
}
Log.Warning( $"Could not find key {key}" );
return 0;
}
[Pure]
[Icon( "description" )]
public string GetDataString( string key )
{
if ( Data.TryGetValue( key, out var value ) )
{
if ( value is string s )
return s;
}
return "";
}
[Pure]
[Icon( "description" )]
public float GetDataFloat( string key )
{
if ( Data.TryGetValue( key, out var value ) )
{
if ( value is float f )
return f;
}
return 0;
}
[Pure]
[Icon( "description" )]
public bool GetDataBool( string key )
{
if ( Data.TryGetValue( key, out var value ) )
{
if ( value is bool b )
return b;
}
return false;
}
[Pure]
[Icon( "description" )]
public T GetData<T>( string key )
{
var obj = Data.GetValueOrDefault( key );
if ( obj == null )
{
Log.Warning( $"Could not find key {key}" );
return default;
}
// i don't even know why this started happening but apparently it sometimes doesn't need to deserialize
if ( obj is T t )
{
return t;
}
if ( obj is not JsonElement jsonElement )
{
Log.Error( $"Arbitrary data {key} on {this} is not a JsonElement: {obj} ({obj.GetType()})" );
return default;
}
return JsonSerializer.Deserialize<T>( jsonElement.GetRawText(), GameManager.JsonOptions );
}
[Pure]
public void SetData( string key, object value )
{
Data[key] = value;
}
public void AddTarget( GameObject target )
{
CurrentTargets ??= new List<GameObject>();
CurrentTargets.Add( target );
}
public void SetTarget( int index, GameObject target )
{
CurrentTargets ??= new List<GameObject>();
if ( index >= CurrentTargets.Count )
{
CurrentTargets.Add( target );
}
else
{
CurrentTargets[index] = target;
}
}
public BaseNpc GetTarget( int index )
{
return CurrentTargets.ElementAtOrDefault( index )?.Components.Get<BaseNpc>();
}
public void ClearTargets()
{
CurrentTargets.Clear();
}
public void SetAction( string key, Action action )
{
Actions[key] = action;
}
/// <summary>
/// Runs an action by key. Add actions with <see cref="SetAction"/>.
/// </summary>
/// <param name="key"></param>
[Property]
[Icon( "rocket_launch" )]
public void RunAction( string key )
{
if ( Actions.TryGetValue( key, out var action ) )
{
action();
}
else
{
Log.Warning( $"Could not find action {key}" );
}
}
/*/// <summary>
/// Searches for a node with the given id recursively and sets it as the current node.
/// </summary>
/// <param name="id"></param>
[Property]
[Icon( "search" )]
public void JumpToId( string id )
{
( Dialogue.DialogueNode node, List<Dialogue.DialogueNode> list ) FindNode( List<Dialogue.DialogueNode> nodes )
{
foreach ( var node in nodes )
{
if ( node.Id == id )
return (node, nodes);
if ( node.Choices.Count > 0 )
{
foreach ( var choice in node.Choices )
{
var found = FindNode( choice.Nodes );
if ( found.node != null )
return found;
}
}
}
return (null, null);
}
var (node, list) = FindNode( Dialogue.Nodes );
if ( node != null )
{
CurrentNode?.OnExit?.Invoke( this, PlayerCharacter.Local, CurrentTargets, CurrentNode, null );
CurrentNodeList = list;
CurrentNodeIndex = list.IndexOf( node );
Log.Info( $"Jumped to node {node.Id}, index {CurrentNodeIndex}/{list.Count}" );
if ( CurrentNode != null )
{
CurrentNode.OnEnter?.Invoke( this, PlayerCharacter.Local, CurrentTargets, CurrentNode, null );
Read();
}
else
{
Log.Error( "JumpToId: No nodes found for choice" );
}
}
else
{
Log.Warning( $"Could not find node with id {id}" );
}
}*/
public void Advance()
{
Log.Info( "Advancing dialogue" );
if ( DialogueNodeCompletedTaskSource != null )
{
Log.Info( "Completing task" );
DialogueNodeCompletedTaskSource.SetResult();
// DialogueNodeCompletedTaskSource = null;
}
else
{
Log.Warning( "No task completion found" );
End();
}
}
/*public void Advance()
{
if ( IsOnLastNode && CurrentNode.Choices.Count == 0 )
{
Log.Info( "Closing window" );
End();
return;
}
Log.Info( $"Choices: {CurrentNode.Choices.Count}, index: {CurrentNodeIndex}/{CurrentNodeList.Count}" );
// go to the next node if there are no choices
if ( CurrentNode.Choices.Count == 0 )
{
CurrentNode.OnExit?.Invoke( this, PlayerCharacter.Local, CurrentTargets, CurrentNode, null );
CurrentNodeIndex++;
if ( CurrentNode != null )
{
// CurrentNode.OnEnter?.Invoke( this, null, null, CurrentNode, null );
// Read();
if ( CurrentNode.IsHidden )
{
Advance();
return;
}
Read();
}
else
{
Log.Error( "Advance: No nodes found for choice" );
}
return;
}
Log.Error( "Choices found" );
}*/
/*private void Read()
{
Log.Info( $"Reading {CurrentNode}" );
Text = "";
_textIndex = 0;
_textTarget = ParseVariables( CurrentNode.Text );
if ( CurrentNode.IsPlayer )
{
Name = "Player";
}
else if ( CurrentTargets.Count > 0 )
{
// var speaker = CurrentTargets.ElementAtOrDefault( CurrentNode.Speaker );
// Name = speaker?.Name ?? "Unknown";
var speaker = CurrentTargets.ElementAtOrDefault( CurrentNode.Speaker );
if ( !speaker.IsValid() )
{
Log.Error( "Speaker is not valid" );
Name = "UNKNOWN";
}
else if ( speaker.Components.TryGet<BaseNpc>( out var npc ) )
{
Name = npc.Name;
}
else if ( speaker.Components.TryGet<PlayerCharacter>( out var player ) )
{
Name = player.PlayerName;
}
else
{
Name = speaker.Name;
}
}
else
{
Log.Error( "No targets found" );
Name = "NO TARGETS";
}
// _skipped = false;
// Panel.FlashClass( "noclick", 0.1f );
}*/
private string ParseVariables( string text )
{
var result = text;
foreach ( var key in Data.Keys )
{
Log.Info( $"Replacing {{{{key}}}} with {Data[key]}" );
result = result.Replace( "{{" + key + "}}", Data[key].ToString() );
}
return result;
}
protected override void OnFixedUpdate()
{
base.OnFixedUpdate();
Typewriter();
}
private void Typewriter()
{
if ( _textTarget == null ) return;
if ( _lastLetter <= 0.05f )
{
return;
}
_lastLetter = 0;
if ( _textIndex >= _textTarget.Length )
{
return;
}
Text += _textTarget[_textIndex];
OnLetterTyped( _textTarget[_textIndex] );
_textIndex++;
}
private char[] _letters = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
private void OnLetterTyped( char letter )
{
switch ( letter )
{
case '1':
letter = 'o';
break;
case '2':
letter = 't';
break;
case '3':
letter = 't';
break;
case '4':
letter = 'f';
break;
case '5':
letter = 'f';
break;
case '6':
letter = 's';
break;
case '7':
letter = 's';
break;
case '8':
letter = 'e';
break;
case '9':
letter = 'n';
break;
case '0':
letter = 'z';
break;
}
if ( !char.IsLetter( letter ) ) return;
var s = SoundFile.Load( "sounds/speech/alphabet/" + letter.ToString().ToUpper() + ".vsnd" );
var h = Sound.PlayFile( s );
h.ListenLocal = true;
h.Pitch = Random.Shared.Float( 1.9f, 2.1f );
}
private void OnClick( PanelEvent e )
{
// if ( _textIndex < 2 ) return;
// Input.ReleaseActions();
e.StopPropagation();
// If we're still typing, finish the text
if ( Text != null && _textTarget != null && Text.Length < _textTarget.Length )
{
Log.Info( "Skipping text" );
// _skipped = true;
_textIndex = _textTarget.Length;
Text = _textTarget;
return;
}
if ( IsCurrentNodeChoice )
{
return;
}
Advance();
}
public void End()
{
Log.Info( "Ending dialogue" );
Enabled = false;
ClearTargets();
CurrentNodeList = null;
CurrentNodeIndex = 0;
// CurrentNode.OnExit?.Invoke( this, null, null, CurrentNode, null );
OnDialogueEnd?.Invoke();
OnDialogueEnd = null;
}
/*private void OnChoice( Dialogue.DialogueChoice choice )
{
Log.Info( $"Selected {choice.Label}" );
if ( choice.OnSelect != null )
{
Log.Info( $"Running custom action for {choice.Label}" );
choice.OnSelect( this, PlayerCharacter.Local, CurrentTargets, CurrentNode, choice );
}
else
{
if ( choice.Nodes.Count == 0 && string.IsNullOrEmpty( choice.JumpToId ) )
{
Log.Error( "OnChoice1: No nodes found for choice" );
End();
return;
}
CurrentNode.OnExit?.Invoke( this, PlayerCharacter.Local, CurrentTargets, CurrentNode, null );
if ( !string.IsNullOrEmpty( choice.JumpToId ) )
{
JumpToId( choice.JumpToId );
return;
}
CurrentNodeList = choice.Nodes;
CurrentNodeIndex = 0;
// CurrentChoice = choice;
if ( CurrentNode != null )
{
CurrentNode.OnEnter?.Invoke( this, PlayerCharacter.Local, CurrentTargets, CurrentNode, null );
Read();
}
else
{
Log.Error( "OnChoice2: No nodes found for choice" );
}
}
}*/
protected override int BuildHash()
{
return HashCode.Combine( Text );
}
private string GetSpeakerName( GameObject speaker )
{
if ( speaker.IsValid() )
{
if ( speaker.Components.TryGet<BaseNpc>( out var npc ) )
{
return npc.Name;
}
else if ( speaker.Components.TryGet<PlayerCharacter>( out var player ) )
{
return player.PlayerName;
}
else
{
return speaker.Name;
}
}
return "UNKNOWN";
}
public void DispatchText( GameObject speaker, string text )
{
Name = GetSpeakerName( speaker );
_textTarget = ParseVariables( text );
_textIndex = 0;
Text = "";
Log.Info( $"Dispatching text: {text}" );
}
}