Files
Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/UX/UXDialogue.cs
2025-06-24 23:49:13 +08:00

400 lines
14 KiB
C#

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using BITKit;
using BITKit.Entities;
using BITKit.Mod;
using BITKit.UX;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Net.BITKit.Chat;
using Net.BITKit.Localization;
using Net.Project.B.Dialogue;
using Project.B.Player;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UIElements;
namespace Net.Project.B.UX
{
public class UXDialogue<THud>:UIToolkitSubPanel<THud>, IUXDialogue where THud:IUXPanel
{
private readonly ILocalizationService _localizationService;
private readonly IMainTicker _ticker;
private readonly ILogger<UXDialogue<THud>> _logger;
private readonly IDialogueService _dialogueService;
private readonly IChatService _chatService;
private readonly ValidHandle _isBusy = new();
private VisualElement _rootVisualElement;
private readonly IEntitiesService _entitiesService;
[UXBindPath("dialogue-container")]
private VisualElement _dialogueContainer;
[UXBindPath("dialogue-choose-container")]
private VisualElement _dialogueChooseContainer;
[UXBindPath("comic-container")]
private VisualElement _comicContainer;
[UXBindPath("tips-foldout")]
private Foldout _tipsFoldout;
private readonly ValidHandle _allowTips = new();
private VisualElement _floatDialogueContainer;
private VisualTreeAsset _dialogueTemplate;
private VisualTreeAsset _dialogueChooseTemplate;
private readonly ConcurrentDictionary<int, VisualElement> _subtitles = new();
private UniTaskCompletionSource<int> _chooseTask;
private readonly ValidHandle _isVisible = new();
private readonly ValidHandle _allowComic = new();
private readonly ValidHandle _inDialogue = new();
private CancellationTokenSource _newDialogueCancellationTokenSource;
private readonly IntervalUpdate _skipInterval = new (0.1f);
private readonly ConcurrentDictionary<int, IDialogueData> _dialogues = new();
private readonly Dictionary<int, Label> _floatDialogues=new();
private readonly Dictionary<int, ValidHandle> _floatDialoguesGc = new();
private readonly IUXKeyMap<InputAction> _uxKeyMap;
private readonly IPlayerKeyMap<InputAction> _playerKeyMap;
public UXDialogue(IServiceProvider serviceProvider, IDialogueService dialogueService, ILogger<UXDialogue<THud>> logger, IEntitiesService entitiesService, IMainTicker ticker, ILocalizationService localizationService, IUXKeyMap<InputAction> uxKeyMap, IPlayerKeyMap<InputAction> playerKeyMap, IChatService chatService) : base(serviceProvider)
{
_dialogueService = dialogueService;
_logger = logger;
_entitiesService = entitiesService;
_ticker = ticker;
_localizationService = localizationService;
_uxKeyMap = uxKeyMap;
_playerKeyMap = playerKeyMap;
_chatService = chatService;
_dialogueService.OnDialogueStart += OnDialogueStart;
_dialogueService.OnDialogueEnd+=OnDialogueEnd;
_dialogueService.OnDialogueChoose += OnDialogueChoose;
_chatService.OnMessageReceived += OnMessageReceived;
ticker.Add(Tick);
}
private void OnMessageReceived(ChatMessage obj)
{
if (obj.FromUserId == Environment.UserName)
{
_allowTips.Clear();
}
}
private void Tick(float obj)
{
foreach (var (id,visualElement) in _floatDialogues)
{
if(_entitiesService.TryGetEntity(id,out var entity) is false)continue;
if (entity.ServiceProvider.QueryComponents(out Collider collider))
{
visualElement.SetPosition(collider.bounds.center + Vector3.up * (collider.bounds.extents.y * 0.32f));
}
else if (entity.ServiceProvider.QueryComponents(out Transform transform))
{
visualElement.SetPosition(transform.position);
}
}
}
protected override async UniTask OnInitiatedAsync()
{
_logger.LogInformation("UX对话初始化中...");
using var _ = _isBusy.GetHandle();
_logger.LogInformation($"{typeof(THud).Name}已初始化完成,正在加载Template...");
_rootVisualElement = Panel.Root as VisualElement;
UXUtils.Inject(this, _rootVisualElement);
_dialogueContainer.Clear();
_dialogueChooseContainer.Clear();
_dialogueTemplate = await ModService.LoadAsset<VisualTreeAsset>("ux_dialogue_template");
_dialogueChooseTemplate = await ModService.LoadAsset<VisualTreeAsset>("ux_dialogue_choose_template");
_logger.LogInformation("UX对话初始化完成");
_dialogueContainer.SetActive(false);
_isVisible.AddListener(_dialogueContainer.SetActive);
_allowComic.AddListener(_comicContainer.SetActive);
_allowComic?.Invoke();
if (Panel is UIToolKitPanel panel)
{
panel.InputActionGroup.RegisterCallback(_playerKeyMap.RunKey, OnConfirm);
_floatDialogueContainer = panel.RootVisualElement.Create<VisualElement>();
_floatDialogueContainer.pickingMode = PickingMode.Ignore;
_floatDialogueContainer.style.position = new StyleEnum<Position>(Position.Absolute);
_floatDialogueContainer.style.top = 0;
_floatDialogueContainer.style.left = 0;
_floatDialogueContainer.style.right = 0;
_floatDialogueContainer.style.bottom = 0;
_floatDialogueContainer.AddToClassList("ui_hud_dialogue_container");
_floatDialogueContainer.SendToBack();
}
_allowTips.AddListener(x =>
{
_tipsFoldout.Blur();
_tipsFoldout.schedule.Execute(() =>
{
if (x)
{
_tipsFoldout.value = false;
_tipsFoldout.SetActive(true);
}
else
{
_tipsFoldout.SetActive(false);
_tipsFoldout.Clear();
}
});
});
_tipsFoldout.Q<Toggle>().focusable = false;
_allowTips.Invoke();
}
private void OnConfirm(InputAction.CallbackContext obj)
{
if(_dialogues.Count is 0)return;
if (obj.JustPressed() is false) return;
if (Panel.Enabled is false) return;
if (_skipInterval.AllowUpdateWithoutReset is false) return;
foreach (var dialogue in _dialogues.Values.ToArray())
{
dialogue.CancellationTokenSource.Cancel();
}
throw new OperationCanceledException();
}
private UniTask<int> OnDialogueChoose(IDialogueData arg1, IReadOnlyCollection<IDialogueChoice> arg2)
{
BITAppForUnity.AllowCursor.AddElement(this);
_chooseTask?.TrySetCanceled();
_chooseTask = new UniTaskCompletionSource<int>();
_dialogueChooseContainer.Clear();
for (var i = 0; i < arg2.Count; i++)
{
var choice = arg2.ElementAt(i);
var container = _dialogueChooseContainer.Create(_dialogueChooseTemplate);
container.Get<Label>().text = i.ToString();
container.Get<Label>(1).text = choice.Text;
var i1 = i;
container.Get<Button>().clicked += () =>
{
_chooseTask.TrySetResult(i1);
BITAppForUnity.AllowCursor.RemoveElement(this);
_dialogueChooseContainer.Clear();
};
}
return _chooseTask.Task;
}
private async void OnDialogueEnd(IDialogueData obj)
{
_dialogues.TryRemove(obj.Identity);
_inDialogue.RemoveElement(obj.Identity);
_allowComic.RemoveElement(obj.Identity);
if (_subtitles.TryGetValue(obj.Identity, out var subtitle) is false) return;
subtitle.SetEnabled(false);
try
{
_newDialogueCancellationTokenSource?.Cancel();
_newDialogueCancellationTokenSource = new();
await UniTask.Delay(5000).AttachExternalCancellation(_newDialogueCancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
}
if (_dialogueChooseContainer.visible is false) return;
subtitle.RemoveFromHierarchy();
_isVisible.RemoveElement(obj.Identity);
if (_floatDialoguesGc.TryGetValue(obj.ActorIdentity, out var gc))
{
gc.RemoveElement(obj.Identity);
}
}
private async UniTask OnDialogueStart(IDialogueData arg)
{
_skipInterval.Reset();
_newDialogueCancellationTokenSource?.Cancel();
await _isBusy;
_dialogues.TryAdd(arg.Identity, arg);
_inDialogue.AddElement(arg.Identity);
switch (arg.MetaObject)
{
case Texture2D texture2D:
{
_allowComic.AddElement(arg.Identity);
_comicContainer.style.backgroundImage = new(texture2D);
if (string.IsNullOrEmpty(arg.Text)) return;
}
break;
case Sprite sprite:
{
_allowComic.AddElement(arg.Identity);
_comicContainer.style.backgroundImage = new(sprite);
if (string.IsNullOrEmpty(arg.Text)) return;
}
break;
}
_isVisible.AddElement(arg.Identity);
var container = _dialogueContainer.Create(_dialogueTemplate);
if (arg.ActorIdentity is not 0 && _entitiesService.TryGetEntity(arg.ActorIdentity, out var entity) &&
entity.ServiceProvider.GetService<IdComponent>() is var id)
{
container.Get<Label>().text = string.IsNullOrEmpty(id.Name) ? string.Empty : _localizationService.GetLocalizedString( id.Name) + ":";
}
else
{
var players = _entitiesService.QueryComponents<IdComponent, LocalPlayerComponent>().ToArray();
container.Get<Label>().text =_localizationService.GetLocalizedString( players.Any() ? players[0].Item1.Name : "#Narrator") + ":";
}
var text = arg.Text;
foreach (var func in OnSubtitle.CastAsFunc())
{
text = func.Invoke(text);
}
var currentText = _localizationService.GetLocalizedString(text);
;
container.Get<Label>(1).text = currentText;
if (container.Get<Label>(2) is { } label)
{
if (SubtitleLanguages is { Length: > 0 })
{
var stringBuilder = new StringBuilder();
foreach (var lang in SubtitleLanguages)
{
if (lang == _localizationService.CurrentLanguage) continue;
var newText = _localizationService.GetLocalizedString(text, lang);
if (newText.Equals(currentText, StringComparison.CurrentCultureIgnoreCase)) continue;
stringBuilder.AppendLine(newText);
}
label.text = stringBuilder.ToString();
label.SetActive(string.IsNullOrEmpty(label.text) is false);
}
else
{
label.SetActive(false);
}
}
_subtitles.TryAdd(arg.Identity, container);
if (_dialogueContainer is ScrollView scrollView)
{
scrollView.ScrollToBottom();
}
if (_entitiesService.Entities.ContainsKey(arg.ActorIdentity))
{
if (_floatDialogues.TryGetValue(arg.ActorIdentity, out var floatLabel) is false)
{
floatLabel = _floatDialogues[arg.ActorIdentity] = _floatDialogueContainer.Create<Label>();
var gc =_floatDialoguesGc[arg.ActorIdentity] = new();
gc.AddListener(x =>
{
if (!x)
{
_floatDialogues.Remove(arg.ActorIdentity);
_floatDialoguesGc.Remove(arg.ActorIdentity);
gc.Dispose();
floatLabel.RemoveFromHierarchy();
}
});
gc.AddElement(arg.Identity);
}
floatLabel.text = _localizationService.GetLocalizedString( arg.Text);
}
if (arg.MetaObject is string tips)
{
_tipsFoldout.Clear();
_tipsFoldout.Create<Label>().text = tips;
_allowTips.Invoke(true);
}
}
public void Dispose()
{
_dialogueService.OnDialogueStart -= OnDialogueStart;
_dialogueService.OnDialogueEnd-=OnDialogueEnd;
_ticker.Remove(Tick);
}
public event Func<string, string> OnSubtitle;
public string[] SubtitleLanguages { get; set; }
}
}