using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Dalamud.Configuration; using Dalamud.Data; using Dalamud.Game; using Dalamud.Game.Chat.SeStringHandling; using Dalamud.Game.Chat.SeStringHandling.Payloads; using Dalamud.Game.ClientState; using Dalamud.Game.Command; using Dalamud.Game.Internal; using Dalamud.Game.Internal.Gui; using Dalamud.Interface; namespace Dalamud.Plugin { /// /// This class acts as an interface to various objects needed to interact with Dalamud and the game. /// public class DalamudPluginInterface : IDisposable { /// /// The reason this plugin was loaded. /// public PluginLoadReason Reason { get; } /// /// The CommandManager object that allows you to add and remove custom chat commands. /// public readonly CommandManager CommandManager; /// /// The ClientState object that allows you to access current client memory information like actors, territories, etc. /// public readonly ClientState ClientState; /// /// The Framework object that allows you to interact with the client. /// public readonly Framework Framework; /// /// A UiBuilder instance which allows you to draw UI into the game via ImGui draw calls. /// public readonly UiBuilder UiBuilder; /// /// A SigScanner instance targeting the main module of the FFXIV process. /// public readonly SigScanner TargetModuleScanner; /// /// A DataManager instance which allows you to access game data needed by the main dalamud features. /// public readonly DataManager Data; /// /// A SeStringManager instance which allows creating and parsing SeString payloads. /// public readonly SeStringManager SeStringManager; private readonly Dalamud dalamud; private readonly string pluginName; private readonly PluginConfigurations configs; /// /// Set up the interface and populate all fields needed. /// /// internal DalamudPluginInterface(Dalamud dalamud, string pluginName, PluginConfigurations configs, PluginLoadReason reason) { Reason = reason; this.CommandManager = dalamud.CommandManager; this.Framework = dalamud.Framework; this.ClientState = dalamud.ClientState; this.UiBuilder = new UiBuilder(dalamud, pluginName); this.TargetModuleScanner = dalamud.SigScanner; this.Data = dalamud.Data; this.SeStringManager = dalamud.SeStringManager; this.dalamud = dalamud; this.pluginName = pluginName; this.configs = configs; } /// /// Unregister your plugin and dispose all references. You have to call this when your IDalamudPlugin is disposed. /// public void Dispose() { this.UiBuilder.Dispose(); this.Framework.Gui.Chat.RemoveChatLinkHandler(this.pluginName); } /// /// Save a plugin configuration(inheriting IPluginConfiguration). /// /// The current configuration. public void SavePluginConfig(IPluginConfiguration currentConfig) { if (currentConfig == null) return; this.configs.Save(currentConfig, this.pluginName); } /// /// Get a previously saved plugin configuration or null if none was saved before. /// /// A previously saved config or null if none was saved before. public IPluginConfiguration GetPluginConfig() { // This is done to support json deserialization of plugin configurations // even after running an in-game update of plugins, where the assembly version // changes. // Eventually it might make sense to have a separate method on this class // T GetPluginConfig() where T : IPluginConfiguration // that can invoke LoadForType() directly instead of via reflection // This is here for now to support the current plugin API foreach (var type in Assembly.GetCallingAssembly().GetTypes()) { if (type.GetInterface(typeof(IPluginConfiguration).FullName) != null) { var mi = this.configs.GetType().GetMethod("LoadForType"); var fn = mi.MakeGenericMethod(type); return (IPluginConfiguration)fn.Invoke(this.configs, new object[] { this.pluginName }); } } // this shouldn't be a thing, I think, but just in case return this.configs.Load(this.pluginName); } /// /// Get the config directory /// /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName public string GetPluginConfigDirectory() => this.configs.GetDirectory(this.pluginName); #region Chat Links /// /// Register a chat link handler. /// /// /// /// Returns an SeString payload for the link. public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action commandAction) { return this.Framework.Gui.Chat.AddChatLinkHandler(this.pluginName, commandId, commandAction); } /// /// Remove a chat link handler. /// /// public void RemoveChatLinkHandler(uint commandId) { this.Framework.Gui.Chat.RemoveChatLinkHandler(this.pluginName, commandId); } /// /// Removes all chat link handlers registered by the plugin. /// public void RemoveChatLinkHandler() { this.Framework.Gui.Chat.RemoveChatLinkHandler(this.pluginName); } #endregion #region IPC internal Action anyPluginIpcAction; /// /// Subscribe to an IPC message by any plugin. /// /// The action to take when a message was received. public void SubscribeAny(Action action) { if (this.anyPluginIpcAction != null) throw new InvalidOperationException("Can't subscribe multiple times."); this.anyPluginIpcAction = action; } /// /// Subscribe to an IPC message by a plugin. /// /// The InternalName of the plugin to subscribe to. /// The action to take when a message was received. public void Subscribe(string pluginName, Action action) { if (this.dalamud.PluginManager.IpcSubscriptions.Any(x => x.SourcePluginName == this.pluginName && x.SubPluginName == pluginName)) throw new InvalidOperationException("Can't add multiple subscriptions for the same plugin."); this.dalamud.PluginManager.IpcSubscriptions.Add((this.pluginName, pluginName, action)); } /// /// Unsubscribe from messages from any plugin. /// public void UnsubscribeAny() { if (this.anyPluginIpcAction == null) throw new InvalidOperationException("Wasn't subscribed to this plugin."); this.anyPluginIpcAction = null; } /// /// Unsubscribe from messages from a plugin. /// /// The InternalName of the plugin to unsubscribe from. public void Unsubscribe(string pluginName) { var sub = this.dalamud.PluginManager.IpcSubscriptions.FirstOrDefault(x => x.SourcePluginName == this.pluginName && x.SubPluginName == pluginName); if (sub.SubAction == null) throw new InvalidOperationException("Wasn't subscribed to this plugin."); this.dalamud.PluginManager.IpcSubscriptions.Remove(sub); } /// /// Send a message to all subscribed plugins. /// /// The message to send. public void SendMessage(ExpandoObject message) { var subs = this.dalamud.PluginManager.IpcSubscriptions.Where(x => x.SubPluginName == this.pluginName); foreach (var sub in subs.Select(x => x.SubAction)) { sub.Invoke(message); } } /// /// Send a message to a specific plugin. /// /// The InternalName of the plugin to send the message to. /// The message to send. /// True if the corresponding plugin was present and received the message. public bool SendMessage(string pluginName, ExpandoObject message) { var (_, _, pluginInterface, _) = this.dalamud.PluginManager.Plugins.FirstOrDefault(x => x.Definition.InternalName == this.pluginName); if (pluginInterface?.anyPluginIpcAction == null) return false; pluginInterface.anyPluginIpcAction.Invoke(this.pluginName, message); return true; } #endregion #region Logging /// /// Log a templated message to the in-game debug log. /// /// The message template. /// Values to log. [Obsolete] public void Log(string messageTemplate, params object[] values) { Serilog.Log.Information(messageTemplate, values); } /// /// Log a templated error message to the in-game debug log. /// /// The message template. /// Values to log. [Obsolete] public void LogError(string messageTemplate, params object[] values) { Serilog.Log.Error(messageTemplate, values); } /// /// Log a templated error message to the in-game debug log. /// /// The exception that caused the error. /// The message template. /// Values to log. [Obsolete] public void LogError(Exception exception, string messageTemplate, params object[] values) { Serilog.Log.Error(exception, messageTemplate, values); } #endregion } }