using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Pal.Client.Rendering; using System; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using Dalamud.Game; using Dalamud.Game.ClientState; using Dalamud.Game.Command; using Dalamud.Game.Gui; using Pal.Client.Properties; using ECommons; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Pal.Client.Commands; using Pal.Client.Configuration; using Pal.Client.DependencyInjection; namespace Pal.Client { /// /// With all DI logic elsewhere, this plugin shell really only takes care of a few things around events that /// need to be sent to different receivers depending on priority or configuration . /// /// internal sealed class Plugin : IDalamudPlugin { private readonly CancellationTokenSource _initCts = new(); private readonly DalamudPluginInterface _pluginInterface; private readonly CommandManager _commandManager; private readonly ClientState _clientState; private readonly ChatGui _chatGui; private readonly Framework _framework; private readonly TaskCompletionSource _rootScopeCompletionSource = new(); private ELoadState _loadState = ELoadState.Initializing; private DependencyInjectionContext? _dependencyInjectionContext; private ILogger _logger = DependencyInjectionContext.LoggerProvider.CreateLogger(); private WindowSystem? _windowSystem; private IServiceScope? _rootScope; private Action? _loginAction; public Plugin( DalamudPluginInterface pluginInterface, CommandManager commandManager, ClientState clientState, ChatGui chatGui, Framework framework) { _pluginInterface = pluginInterface; _commandManager = commandManager; _clientState = clientState; _chatGui = chatGui; _framework = framework; // set up the current UI language before creating anything Localization.Culture = new CultureInfo(_pluginInterface.UiLanguage); _commandManager.AddHandler("/pal", new CommandInfo(OnCommand) { HelpMessage = Localization.Command_pal_HelpText }); Task.Run(async () => await CreateDependencyContext()); } public string Name => Localization.Palace_Pal; private async Task CreateDependencyContext() { try { _dependencyInjectionContext = _pluginInterface.Create(this) ?? throw new Exception("Could not create DI root context class"); var serviceProvider = _dependencyInjectionContext.BuildServiceContainer(); _initCts.Token.ThrowIfCancellationRequested(); _logger = serviceProvider.GetRequiredService>(); _windowSystem = serviceProvider.GetRequiredService(); _rootScope = serviceProvider.CreateScope(); var loader = _rootScope.ServiceProvider.GetRequiredService(); await loader.InitializeAsync(_initCts.Token); await _framework.RunOnFrameworkThread(() => { _pluginInterface.UiBuilder.Draw += Draw; _pluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi; _pluginInterface.LanguageChanged += LanguageChanged; _clientState.Login += Login; }); _rootScopeCompletionSource.SetResult(_rootScope); _loadState = ELoadState.Loaded; } catch (ObjectDisposedException e) { _rootScopeCompletionSource.SetException(e); _loadState = ELoadState.Error; } catch (OperationCanceledException e) { _rootScopeCompletionSource.SetException(e); _loadState = ELoadState.Error; } catch (Exception e) { _rootScopeCompletionSource.SetException(e); _logger.LogError(e, "Async load failed"); ShowErrorOnLogin(() => new Chat(_chatGui).Error(string.Format(Localization.Error_LoadFailed, $"{e.GetType()} - {e.Message}"))); _loadState = ELoadState.Error; } } private void ShowErrorOnLogin(Action? loginAction) { if (_clientState.IsLoggedIn) { loginAction?.Invoke(); _loginAction = null; } else _loginAction = loginAction; } private void Login(object? sender, EventArgs eventArgs) { _loginAction?.Invoke(); _loginAction = null; } private void OnCommand(string command, string arguments) { arguments = arguments.Trim(); Task.Run(async () => { IServiceScope rootScope; try { rootScope = await _rootScopeCompletionSource.Task; } catch (Exception e) { _logger.LogError(e, "Could not wait for command root scope"); return; } IPalacePalConfiguration configuration = rootScope.ServiceProvider.GetRequiredService(); Chat chat = rootScope.ServiceProvider.GetRequiredService(); if (configuration.FirstUse && arguments != "" && arguments != "config") { chat.Error(Localization.Error_FirstTimeSetupRequired); return; } try { var sp = rootScope.ServiceProvider; switch (arguments) { case "": case "config": sp.GetRequiredService().Execute(); break; case "stats": sp.GetRequiredService().Execute(); break; case "tc": case "test-connection": sp.GetRequiredService().Execute(); break; case "near": case "tnear": case "hnear": sp.GetRequiredService().Execute(arguments); break; default: chat.Error(string.Format(Localization.Command_pal_UnknownSubcommand, arguments, command)); break; } } catch (Exception e) { chat.Error(e.ToString()); } }); } private void OpenConfigUi() => _rootScope!.ServiceProvider.GetRequiredService().Execute(); private void LanguageChanged(string languageCode) { _logger.LogInformation("Language set to '{Language}'", languageCode); Localization.Culture = new CultureInfo(languageCode); _windowSystem!.Windows.OfType() .Each(w => w.LanguageChanged()); } private void Draw() { _rootScope!.ServiceProvider.GetRequiredService().DrawLayers(); _windowSystem!.Draw(); } public void Dispose() { _commandManager.RemoveHandler("/pal"); if (_loadState == ELoadState.Loaded) { _pluginInterface.UiBuilder.Draw -= Draw; _pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi; _pluginInterface.LanguageChanged -= LanguageChanged; _clientState.Login -= Login; } _initCts.Cancel(); _rootScope?.Dispose(); } private enum ELoadState { Initializing, Loaded, Error } } }