mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-02 05:43:40 +01:00
Merge branch 'apiX' into feature/itextureprovider-updates
This commit is contained in:
commit
8c7771bf7d
2213 changed files with 10372 additions and 1088868 deletions
|
|
@ -128,6 +128,11 @@ public sealed class DalamudPluginInterface : IDisposable
|
|||
/// </summary>
|
||||
public string InternalName => this.plugin.InternalName;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's manifest.
|
||||
/// </summary>
|
||||
public IPluginManifest Manifest => this.plugin.Manifest;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this is a dev plugin.
|
||||
/// </summary>
|
||||
|
|
@ -452,26 +457,28 @@ public sealed class DalamudPluginInterface : IDisposable
|
|||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Unregister your plugin and dispose all references.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="Dispose"/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
this.UiBuilder.ExplicitDispose();
|
||||
Service<ChatGui>.Get().RemoveChatLinkHandler(this.plugin.InternalName);
|
||||
Service<Localization>.Get().LocalizationChanged -= this.OnLocalizationChanged;
|
||||
Service<DalamudConfiguration>.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete implicit dispose implementation. Should not be used.
|
||||
/// </summary>
|
||||
[Obsolete("Do not dispose \"DalamudPluginInterface\".", true)]
|
||||
/// <summary>This function will do nothing. Dalamud will dispose this object on plugin unload.</summary>
|
||||
[Obsolete("This function will do nothing. Dalamud will dispose this object on plugin unload.", true)]
|
||||
public void Dispose()
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
/// <summary>Unregister the plugin and dispose all references.</summary>
|
||||
/// <remarks>Dalamud internal use only.</remarks>
|
||||
internal void DisposeInternal()
|
||||
{
|
||||
Service<ChatGui>.Get().RemoveChatLinkHandler(this.plugin.InternalName);
|
||||
Service<Localization>.Get().LocalizationChanged -= this.OnLocalizationChanged;
|
||||
Service<DalamudConfiguration>.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
|
||||
this.UiBuilder.DisposeInternal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatch the active plugins changed event.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -39,23 +39,11 @@ namespace Dalamud.Plugin.Internal;
|
|||
|
||||
/// <summary>
|
||||
/// Class responsible for loading and unloading plugins.
|
||||
/// NOTE: ALL plugin exposed services are marked as dependencies for PluginManager in Service{T}.
|
||||
/// NOTE: ALL plugin exposed services are marked as dependencies for <see cref="PluginManager"/>
|
||||
/// from <see cref="ResolvePossiblePluginDependencyServices"/>.
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
|
||||
// DalamudTextureWrap registers textures to dispose with IM
|
||||
[InherentDependency<InterfaceManager>]
|
||||
|
||||
// LocalPlugin uses ServiceContainer to create scopes
|
||||
[InherentDependency<ServiceContainer>]
|
||||
|
||||
// DalamudPluginInterface hands out a reference to this, so we have to keep it around
|
||||
// TODO api9: make it a service
|
||||
[InherentDependency<DataShare>]
|
||||
|
||||
#pragma warning restore SA1015
|
||||
internal partial class PluginManager : IDisposable, IServiceType
|
||||
[ServiceManager.BlockingEarlyLoadedService("Accomodation of plugins that blocks the game startup.")]
|
||||
internal partial class PluginManager : IInternalDisposableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Default time to wait between plugin unload and plugin assembly unload.
|
||||
|
|
@ -72,7 +60,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
private readonly List<RemotePluginManifest> availablePluginsList = new();
|
||||
private readonly List<AvailablePluginUpdate> updatablePluginsList = new();
|
||||
|
||||
private readonly DalamudLinkPayload openInstallerWindowPluginChangelogsLink;
|
||||
private readonly Task<DalamudLinkPayload> openInstallerWindowPluginChangelogsLink;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
|
@ -86,9 +74,6 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly HappyHttpClient happyHttpClient = Service<HappyHttpClient>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ChatGui chatGui = Service<ChatGui>.Get();
|
||||
|
||||
static PluginManager()
|
||||
{
|
||||
DalamudApiLevel = typeof(PluginManager).Assembly.GetName().Version!.Major;
|
||||
|
|
@ -137,15 +122,22 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
throw new InvalidDataException("Couldn't deserialize banned plugins manifest.");
|
||||
}
|
||||
|
||||
this.openInstallerWindowPluginChangelogsLink = this.chatGui.AddChatLinkHandler("Dalamud", 1003, (_, _) =>
|
||||
{
|
||||
Service<DalamudInterface>.GetNullable()?.OpenPluginInstallerTo(PluginInstallerWindow.PluginInstallerOpenKind.Changelogs);
|
||||
});
|
||||
this.openInstallerWindowPluginChangelogsLink =
|
||||
Service<ChatGui>.GetAsync().ContinueWith(
|
||||
chatGuiTask => chatGuiTask.Result.AddChatLinkHandler(
|
||||
"Dalamud",
|
||||
1003,
|
||||
(_, _) =>
|
||||
{
|
||||
Service<DalamudInterface>.GetNullable()?.OpenPluginInstallerTo(
|
||||
PluginInstallerWindow.PluginInstallerOpenKind.Changelogs);
|
||||
}));
|
||||
|
||||
this.configuration.PluginTestingOptIns ??= new();
|
||||
this.MainRepo = PluginRepository.CreateMainRepo(this.happyHttpClient);
|
||||
|
||||
this.ApplyPatches();
|
||||
// NET8 CHORE
|
||||
// this.ApplyPatches();
|
||||
|
||||
registerStartupBlocker(
|
||||
Task.Run(this.LoadAndStartLoadSyncPlugins),
|
||||
|
|
@ -303,41 +295,54 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
/// <param name="updateMetadata">The list of updated plugin metadata.</param>
|
||||
/// <param name="header">The header text to send to chat prior to any update info.</param>
|
||||
public void PrintUpdatedPlugins(List<PluginUpdateStatus>? updateMetadata, string header)
|
||||
{
|
||||
if (updateMetadata is { Count: > 0 })
|
||||
{
|
||||
this.chatGui.Print(new XivChatEntry
|
||||
=> Service<ChatGui>.GetAsync().ContinueWith(
|
||||
chatGuiTask =>
|
||||
{
|
||||
Message = new SeString(new List<Payload>()
|
||||
{
|
||||
new TextPayload(header),
|
||||
new TextPayload(" ["),
|
||||
new UIForegroundPayload(500),
|
||||
this.openInstallerWindowPluginChangelogsLink,
|
||||
new TextPayload(Loc.Localize("DalamudInstallerPluginChangelogHelp", "Open plugin changelogs")),
|
||||
RawPayload.LinkTerminator,
|
||||
new UIForegroundPayload(0),
|
||||
new TextPayload("]"),
|
||||
}),
|
||||
});
|
||||
if (!chatGuiTask.IsCompletedSuccessfully)
|
||||
return;
|
||||
|
||||
foreach (var metadata in updateMetadata)
|
||||
{
|
||||
if (metadata.Status == PluginUpdateStatus.StatusKind.Success)
|
||||
var chatGui = chatGuiTask.Result;
|
||||
if (updateMetadata is { Count: > 0 })
|
||||
{
|
||||
this.chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.chatGui.Print(new XivChatEntry
|
||||
chatGui.Print(
|
||||
new XivChatEntry
|
||||
{
|
||||
Message = new SeString(
|
||||
new List<Payload>()
|
||||
{
|
||||
new TextPayload(header),
|
||||
new TextPayload(" ["),
|
||||
new UIForegroundPayload(500),
|
||||
this.openInstallerWindowPluginChangelogsLink.Result,
|
||||
new TextPayload(
|
||||
Loc.Localize("DalamudInstallerPluginChangelogHelp", "Open plugin changelogs")),
|
||||
RawPayload.LinkTerminator,
|
||||
new UIForegroundPayload(0),
|
||||
new TextPayload("]"),
|
||||
}),
|
||||
});
|
||||
|
||||
foreach (var metadata in updateMetadata)
|
||||
{
|
||||
Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version, PluginUpdateStatus.LocalizeUpdateStatusKind(metadata.Status)),
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
if (metadata.Status == PluginUpdateStatus.StatusKind.Success)
|
||||
{
|
||||
chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version));
|
||||
}
|
||||
else
|
||||
{
|
||||
chatGui.Print(
|
||||
new XivChatEntry
|
||||
{
|
||||
Message = Locs.DalamudPluginUpdateFailed(
|
||||
metadata.Name,
|
||||
metadata.Version,
|
||||
PluginUpdateStatus.LocalizeUpdateStatusKind(metadata.Status)),
|
||||
Type = XivChatType.Urgent,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// For a given manifest, determine if the user opted into testing this plugin.
|
||||
|
|
@ -370,7 +375,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
var disposablePlugins =
|
||||
this.installedPluginsList.Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError).ToArray();
|
||||
|
|
@ -410,11 +415,21 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
// Now that we've waited enough, dispose the whole plugin.
|
||||
// Since plugins should have been unloaded above, this should be done quickly.
|
||||
foreach (var plugin in disposablePlugins)
|
||||
plugin.ExplicitDisposeIgnoreExceptions($"Error disposing {plugin.Name}", Log);
|
||||
{
|
||||
try
|
||||
{
|
||||
plugin.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Error disposing {plugin.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.assemblyLocationMonoHook?.Dispose();
|
||||
this.assemblyCodeBaseMonoHook?.Dispose();
|
||||
// NET8 CHORE
|
||||
// this.assemblyLocationMonoHook?.Dispose();
|
||||
// this.assemblyCodeBaseMonoHook?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -459,10 +474,18 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
try
|
||||
{
|
||||
var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll"));
|
||||
var manifestFile = LocalPluginManifest.GetManifestFile(dllFile);
|
||||
|
||||
if (!manifestFile.Exists)
|
||||
if (!dllFile.Exists)
|
||||
{
|
||||
Log.Error("No DLL found for plugin at {Path}", versionDir.FullName);
|
||||
continue;
|
||||
}
|
||||
|
||||
var manifestFile = LocalPluginManifest.GetManifestFile(dllFile);
|
||||
if (!manifestFile.Exists)
|
||||
{
|
||||
Log.Error("No manifest for plugin at {Path}", dllFile.FullName);
|
||||
continue;
|
||||
}
|
||||
|
||||
var manifest = LocalPluginManifest.Load(manifestFile);
|
||||
if (manifest == null)
|
||||
|
|
@ -483,6 +506,12 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
}
|
||||
|
||||
this.configuration.QueueSave();
|
||||
|
||||
if (versionsDefs.Count == 0)
|
||||
{
|
||||
Log.Verbose("No versions found for plugin: {Name}", pluginDir.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -833,7 +862,8 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
this.installedPluginsList.Remove(plugin);
|
||||
}
|
||||
|
||||
PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _);
|
||||
// NET8 CHORE
|
||||
// PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _);
|
||||
|
||||
this.NotifyinstalledPluginsListChanged();
|
||||
this.NotifyAvailablePluginsChanged();
|
||||
|
|
@ -1231,6 +1261,16 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
/// <returns>The dependency services.</returns>
|
||||
private static IEnumerable<Type> ResolvePossiblePluginDependencyServices()
|
||||
{
|
||||
// DalamudPluginInterface hands out a reference to this, so we have to keep it around.
|
||||
// TODO api9: make it a service
|
||||
yield return typeof(DataShare);
|
||||
|
||||
// DalamudTextureWrap registers textures to dispose with IM.
|
||||
yield return typeof(InterfaceManager);
|
||||
|
||||
// Note: LocalPlugin uses ServiceContainer to create scopes, but it is done outside PM ctor.
|
||||
// This is not required: yield return typeof(ServiceContainer);
|
||||
|
||||
foreach (var serviceType in ServiceManager.GetConcreteServiceTypes())
|
||||
{
|
||||
if (serviceType == typeof(PluginManager))
|
||||
|
|
@ -1329,12 +1369,25 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
{
|
||||
try
|
||||
{
|
||||
// We don't need to apply, it doesn't matter
|
||||
await this.profileManager.DefaultProfile.RemoveByInternalNameAsync(repoManifest.InternalName, false);
|
||||
// Only remove entries from the default profile that are NOT currently tied to an active LocalPlugin
|
||||
var guidsToRemove = this.profileManager.DefaultProfile.Plugins
|
||||
.Where(x => this.InstalledPlugins.All(y => y.Manifest.WorkingPluginId != x.WorkingPluginId))
|
||||
.Select(x => x.WorkingPluginId)
|
||||
.ToArray();
|
||||
|
||||
if (guidsToRemove.Length != 0)
|
||||
{
|
||||
Log.Verbose("Removing {Cnt} orphaned entries from default profile", guidsToRemove.Length);
|
||||
foreach (var guid in guidsToRemove)
|
||||
{
|
||||
// We don't need to apply, it doesn't matter
|
||||
await this.profileManager.DefaultProfile.RemoveAsync(guid, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ProfileOperationException)
|
||||
catch (ProfileOperationException ex)
|
||||
{
|
||||
// ignored
|
||||
Log.Error(ex, "Error during default profile cleanup");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -1574,7 +1627,8 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
}
|
||||
catch (InvalidPluginException)
|
||||
{
|
||||
PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _);
|
||||
// NET8 CHORE
|
||||
// PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _);
|
||||
throw;
|
||||
}
|
||||
catch (BannedPluginException)
|
||||
|
|
@ -1620,7 +1674,8 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
}
|
||||
else
|
||||
{
|
||||
PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _);
|
||||
// NET8 CHORE
|
||||
// PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
@ -1747,6 +1802,8 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
}
|
||||
}
|
||||
|
||||
// NET8 CHORE
|
||||
/*
|
||||
/// <summary>
|
||||
/// Class responsible for loading and unloading plugins.
|
||||
/// This contains the assembly patching functionality to resolve assembly locations.
|
||||
|
|
@ -1854,3 +1911,4 @@ internal partial class PluginManager
|
|||
this.assemblyCodeBaseMonoHook = new MonoMod.RuntimeDetour.Hook(codebaseTarget, codebasePatch);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
194
Dalamud/Plugin/Internal/PluginValidator.cs
Normal file
194
Dalamud/Plugin/Internal/PluginValidator.cs
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
|
||||
namespace Dalamud.Plugin.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Class responsible for validating a dev plugin.
|
||||
/// </summary>
|
||||
internal static class PluginValidator
|
||||
{
|
||||
private static readonly char[] LineSeparator = new[] { ' ', '\n', '\r' };
|
||||
|
||||
/// <summary>
|
||||
/// Represents the severity of a validation problem.
|
||||
/// </summary>
|
||||
public enum ValidationSeverity
|
||||
{
|
||||
/// <summary>
|
||||
/// The problem is informational.
|
||||
/// </summary>
|
||||
Information,
|
||||
|
||||
/// <summary>
|
||||
/// The problem is a warning.
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// The problem is fatal.
|
||||
/// </summary>
|
||||
Fatal,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a validation problem.
|
||||
/// </summary>
|
||||
public interface IValidationProblem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the severity of the validation.
|
||||
/// </summary>
|
||||
public ValidationSeverity Severity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Compute the localized description of the problem.
|
||||
/// </summary>
|
||||
/// <returns>Localized string to be shown to the developer.</returns>
|
||||
public string GetLocalizedDescription();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for problems in a plugin.
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin to validate.</param>
|
||||
/// <returns>An list of problems.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the plugin is not loaded. A plugin must be loaded to validate it.</exception>
|
||||
public static IReadOnlyList<IValidationProblem> CheckForProblems(LocalDevPlugin plugin)
|
||||
{
|
||||
var problems = new List<IValidationProblem>();
|
||||
|
||||
if (!plugin.IsLoaded)
|
||||
throw new InvalidOperationException("Plugin must be loaded to validate.");
|
||||
|
||||
if (!plugin.DalamudInterface!.UiBuilder.HasConfigUi)
|
||||
problems.Add(new NoConfigUiProblem());
|
||||
|
||||
if (!plugin.DalamudInterface.UiBuilder.HasMainUi)
|
||||
problems.Add(new NoMainUiProblem());
|
||||
|
||||
var cmdManager = Service<CommandManager>.Get();
|
||||
foreach (var cmd in cmdManager.Commands.Where(x => x.Value.LoaderAssemblyName == plugin.InternalName && x.Value.ShowInHelp))
|
||||
{
|
||||
if (string.IsNullOrEmpty(cmd.Value.HelpMessage))
|
||||
problems.Add(new CommandWithoutHelpTextProblem(cmd.Key));
|
||||
}
|
||||
|
||||
if (plugin.Manifest.Tags == null || plugin.Manifest.Tags.Count == 0)
|
||||
problems.Add(new NoTagsProblem());
|
||||
|
||||
if (string.IsNullOrEmpty(plugin.Manifest.Description) || plugin.Manifest.Description.Split(LineSeparator, StringSplitOptions.RemoveEmptyEntries).Length <= 1)
|
||||
problems.Add(new NoDescriptionProblem());
|
||||
|
||||
if (string.IsNullOrEmpty(plugin.Manifest.Punchline))
|
||||
problems.Add(new NoPunchlineProblem());
|
||||
|
||||
if (string.IsNullOrEmpty(plugin.Manifest.Name))
|
||||
problems.Add(new NoNameProblem());
|
||||
|
||||
if (string.IsNullOrEmpty(plugin.Manifest.Author))
|
||||
problems.Add(new NoAuthorProblem());
|
||||
|
||||
return problems;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where the plugin does not have a config UI callback.
|
||||
/// </summary>
|
||||
public class NoConfigUiProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Warning;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "The plugin does not register a config UI callback. If you have a settings window or section, please consider registering UiBuilder.OpenConfigUi to open it.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where the plugin does not have a main UI callback.
|
||||
/// </summary>
|
||||
public class NoMainUiProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Warning;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "The plugin does not register a main UI callback. If your plugin has a window that could be considered the main entrypoint to its features, please consider registering UiBuilder.OpenMainUi to open the plugin's main window.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a command does not have a help text.
|
||||
/// </summary>
|
||||
/// <param name="commandName">Name of the command.</param>
|
||||
public class CommandWithoutHelpTextProblem(string commandName) : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Fatal;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => $"The plugin has a command ({commandName}) without a help message. Please consider adding a help message to the command when registering it.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a plugin does not have any tags in its manifest.
|
||||
/// </summary>
|
||||
public class NoTagsProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Information;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "Your plugin does not have any tags in its manifest. Please consider adding some to make it easier for users to find your plugin in the installer.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a plugin does not have a description in its manifest.
|
||||
/// </summary>
|
||||
public class NoDescriptionProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Information;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "Your plugin does not have a description in its manifest, or it is very terse. Please consider adding one to give users more information about your plugin.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a plugin has no punchline in its manifest.
|
||||
/// </summary>
|
||||
public class NoPunchlineProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Information;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "Your plugin does not have a punchline in its manifest. Please consider adding one to give users a quick overview of what your plugin does.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a plugin has no name in its manifest.
|
||||
/// </summary>
|
||||
public class NoNameProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Fatal;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "Your plugin does not have a name in its manifest.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representing a problem where a plugin has no author in its manifest.
|
||||
/// </summary>
|
||||
public class NoAuthorProblem : IValidationProblem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ValidationSeverity Severity => ValidationSeverity.Fatal;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetLocalizedDescription() => "Your plugin does not have an author in its manifest.";
|
||||
}
|
||||
}
|
||||
|
|
@ -183,10 +183,13 @@ internal class Profile
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Log.Information("Adding plugin {Plugin}({Guid}) to profile {Profile} with state {State}", internalName, workingPluginId, this.Guid, state);
|
||||
|
||||
// We need to remove this plugin from the default profile, if it declares it.
|
||||
if (!this.IsDefaultProfile && this.manager.DefaultProfile.WantsPlugin(workingPluginId) != null)
|
||||
{
|
||||
Log.Information("=> Removing plugin {Plugin}({Guid}) from default profile", internalName, workingPluginId);
|
||||
await this.manager.DefaultProfile.RemoveAsync(workingPluginId, false);
|
||||
}
|
||||
|
||||
|
|
@ -202,8 +205,12 @@ internal class Profile
|
|||
/// </summary>
|
||||
/// <param name="workingPluginId">The ID of the plugin.</param>
|
||||
/// <param name="apply">Whether or not the current state should immediately be applied.</param>
|
||||
/// <param name="checkDefault">
|
||||
/// Whether or not to throw when a plugin is removed from the default profile, without being in another profile.
|
||||
/// Used to prevent orphan plugins, but can be ignored when cleaning up old entries.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task RemoveAsync(Guid workingPluginId, bool apply = true)
|
||||
public async Task RemoveAsync(Guid workingPluginId, bool apply = true, bool checkDefault = true)
|
||||
{
|
||||
ProfileModelV1.ProfileModelV1Plugin entry;
|
||||
lock (this)
|
||||
|
|
@ -215,15 +222,18 @@ internal class Profile
|
|||
if (!this.modelV1.Plugins.Remove(entry))
|
||||
throw new Exception("Couldn't remove plugin from model collection");
|
||||
}
|
||||
|
||||
Log.Information("Removing plugin {Plugin}({Guid}) from profile {Profile}", entry.InternalName, entry.WorkingPluginId, this.Guid);
|
||||
|
||||
// We need to add this plugin back to the default profile, if we were the last profile to have it.
|
||||
if (!this.manager.IsInAnyProfile(workingPluginId))
|
||||
{
|
||||
if (!this.IsDefaultProfile)
|
||||
{
|
||||
Log.Information("=> Adding plugin {Plugin}({Guid}) back to default profile", entry.InternalName, entry.WorkingPluginId);
|
||||
await this.manager.DefaultProfile.AddOrUpdateAsync(workingPluginId, entry.InternalName, this.IsEnabled && entry.IsEnabled, false);
|
||||
}
|
||||
else
|
||||
else if (checkDefault)
|
||||
{
|
||||
throw new PluginNotInDefaultProfileException(workingPluginId.ToString());
|
||||
}
|
||||
|
|
@ -235,31 +245,6 @@ internal class Profile
|
|||
await this.manager.ApplyAllWantStatesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a plugin from this profile.
|
||||
/// This will block until all states have been applied.
|
||||
/// </summary>
|
||||
/// <param name="internalName">The internal name of the plugin.</param>
|
||||
/// <param name="apply">Whether or not the current state should immediately be applied.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task RemoveByInternalNameAsync(string internalName, bool apply = true)
|
||||
{
|
||||
Guid? pluginToRemove = null;
|
||||
lock (this)
|
||||
{
|
||||
foreach (var plugin in this.Plugins)
|
||||
{
|
||||
if (plugin.InternalName.Equals(internalName, StringComparison.Ordinal))
|
||||
{
|
||||
pluginToRemove = plugin.WorkingPluginId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.RemoveAsync(pluginToRemove ?? throw new PluginNotFoundException(internalName), apply);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function tries to migrate all plugins with this internalName which do not have
|
||||
/// a GUID to the specified GUID.
|
||||
|
|
|
|||
|
|
@ -16,8 +16,18 @@ namespace Dalamud.Plugin.Internal.Profiles;
|
|||
/// Service responsible for profile-related chat commands.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class ProfileCommandHandler : IServiceType, IDisposable
|
||||
internal class ProfileCommandHandler : IInternalDisposableService
|
||||
{
|
||||
#pragma warning disable SA1600
|
||||
public const string CommandEnable = "/xlenablecollection";
|
||||
public const string CommandDisable = "/xldisablecollection";
|
||||
public const string CommandToggle = "/xltogglecollection";
|
||||
#pragma warning restore SA1600
|
||||
|
||||
private static readonly string LegacyCommandEnable = CommandEnable.Replace("collection", "profile");
|
||||
private static readonly string LegacyCommandDisable = CommandDisable.Replace("collection", "profile");
|
||||
private static readonly string LegacyCommandToggle = CommandToggle.Replace("collection", "profile");
|
||||
|
||||
private readonly CommandManager cmd;
|
||||
private readonly ProfileManager profileManager;
|
||||
private readonly ChatGui chat;
|
||||
|
|
@ -40,23 +50,38 @@ internal class ProfileCommandHandler : IServiceType, IDisposable
|
|||
this.chat = chat;
|
||||
this.framework = framework;
|
||||
|
||||
this.cmd.AddHandler("/xlenableprofile", new CommandInfo(this.OnEnableProfile)
|
||||
this.cmd.AddHandler(CommandEnable, new CommandInfo(this.OnEnableProfile)
|
||||
{
|
||||
HelpMessage = Loc.Localize("ProfileCommandsEnableHint", "Enable a collection. Usage: /xlenablecollection \"Collection Name\""),
|
||||
ShowInHelp = true,
|
||||
});
|
||||
|
||||
this.cmd.AddHandler("/xldisableprofile", new CommandInfo(this.OnDisableProfile)
|
||||
this.cmd.AddHandler(CommandDisable, new CommandInfo(this.OnDisableProfile)
|
||||
{
|
||||
HelpMessage = Loc.Localize("ProfileCommandsDisableHint", "Disable a collection. Usage: /xldisablecollection \"Collection Name\""),
|
||||
ShowInHelp = true,
|
||||
});
|
||||
|
||||
this.cmd.AddHandler("/xltoggleprofile", new CommandInfo(this.OnToggleProfile)
|
||||
this.cmd.AddHandler(CommandToggle, new CommandInfo(this.OnToggleProfile)
|
||||
{
|
||||
HelpMessage = Loc.Localize("ProfileCommandsToggleHint", "Toggle a collection. Usage: /xltogglecollection \"Collection Name\""),
|
||||
ShowInHelp = true,
|
||||
});
|
||||
|
||||
this.cmd.AddHandler(LegacyCommandEnable, new CommandInfo(this.OnEnableProfile)
|
||||
{
|
||||
ShowInHelp = false,
|
||||
});
|
||||
|
||||
this.cmd.AddHandler(LegacyCommandDisable, new CommandInfo(this.OnDisableProfile)
|
||||
{
|
||||
ShowInHelp = true,
|
||||
});
|
||||
|
||||
this.cmd.AddHandler(LegacyCommandToggle, new CommandInfo(this.OnToggleProfile)
|
||||
{
|
||||
ShowInHelp = true,
|
||||
});
|
||||
|
||||
this.framework.Update += this.FrameworkOnUpdate;
|
||||
}
|
||||
|
|
@ -69,11 +94,14 @@ internal class ProfileCommandHandler : IServiceType, IDisposable
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.cmd.RemoveHandler("/xlenablecollection");
|
||||
this.cmd.RemoveHandler("/xldisablecollection");
|
||||
this.cmd.RemoveHandler("/xltogglecollection");
|
||||
this.cmd.RemoveHandler(CommandEnable);
|
||||
this.cmd.RemoveHandler(CommandDisable);
|
||||
this.cmd.RemoveHandler(CommandToggle);
|
||||
this.cmd.RemoveHandler(LegacyCommandEnable);
|
||||
this.cmd.RemoveHandler(LegacyCommandDisable);
|
||||
this.cmd.RemoveHandler(LegacyCommandToggle);
|
||||
|
||||
this.framework.Update += this.FrameworkOnUpdate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Dalamud.Plugin.Internal.Profiles;
|
|||
/// <summary>
|
||||
/// Class responsible for managing plugin profiles.
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
[ServiceManager.BlockingEarlyLoadedService($"Data provider for {nameof(PluginManager)}.")]
|
||||
internal class ProfileManager : IServiceType
|
||||
{
|
||||
private static readonly ModuleLog Log = new("PROFMAN");
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal.Types.Manifest;
|
||||
|
||||
|
|
@ -98,6 +100,11 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable
|
|||
/// Gets an ID uniquely identifying this specific instance of a devPlugin.
|
||||
/// </summary>
|
||||
public Guid DevImposedWorkingPluginId => this.devSettings.WorkingPluginId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of validation problems that have been dismissed by the user.
|
||||
/// </summary>
|
||||
public List<string> DismissedValidationProblems => this.devSettings.DismissedValidationProblems;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public new void Dispose()
|
||||
|
|
|
|||
|
|
@ -4,11 +4,9 @@ using System.Reflection;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Common.Game;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Gui.Dtr;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
|
@ -240,7 +238,7 @@ internal class LocalPlugin : IDisposable
|
|||
this.instance = null;
|
||||
}
|
||||
|
||||
this.DalamudInterface?.ExplicitDispose();
|
||||
this.DalamudInterface?.DisposeInternal();
|
||||
this.DalamudInterface = null;
|
||||
|
||||
this.ServiceScope?.Dispose();
|
||||
|
|
@ -404,7 +402,8 @@ internal class LocalPlugin : IDisposable
|
|||
}
|
||||
|
||||
// Update the location for the Location and CodeBase patches
|
||||
PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new PluginPatchData(this.DllFile);
|
||||
// NET8 CHORE
|
||||
// PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new PluginPatchData(this.DllFile);
|
||||
|
||||
this.DalamudInterface =
|
||||
new DalamudPluginInterface(this, reason);
|
||||
|
|
@ -426,7 +425,7 @@ internal class LocalPlugin : IDisposable
|
|||
if (this.instance == null)
|
||||
{
|
||||
this.State = PluginState.LoadError;
|
||||
this.DalamudInterface.ExplicitDispose();
|
||||
this.DalamudInterface.DisposeInternal();
|
||||
Log.Error(
|
||||
$"Error while loading {this.Name}, failed to bind and call the plugin constructor");
|
||||
return;
|
||||
|
|
@ -499,7 +498,7 @@ internal class LocalPlugin : IDisposable
|
|||
|
||||
this.instance = null;
|
||||
|
||||
this.DalamudInterface?.ExplicitDispose();
|
||||
this.DalamudInterface?.DisposeInternal();
|
||||
this.DalamudInterface = null;
|
||||
|
||||
this.ServiceScope?.Dispose();
|
||||
|
|
@ -628,12 +627,6 @@ internal class LocalPlugin : IDisposable
|
|||
config.LoadInMemory = true;
|
||||
config.PreferSharedTypes = false;
|
||||
|
||||
// Pin Lumina and its dependencies recursively (compatibility behavior).
|
||||
// It currently only pulls in System.* anyway.
|
||||
// TODO(api10): Remove this. We don't want to pin Lumina anymore, plugins should be able to provide their own.
|
||||
config.SharedAssemblies.Add((typeof(Lumina.GameData).Assembly.GetName(), true));
|
||||
config.SharedAssemblies.Add((typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName(), true));
|
||||
|
||||
// Make sure that plugins do not load their own Dalamud assembly.
|
||||
// We do not pin this recursively; if a plugin loads its own assembly of Dalamud, it is always wrong,
|
||||
// but plugins may load other versions of assemblies that Dalamud depends on.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ internal record RemotePluginManifest : PluginManifest
|
|||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public PluginRepository SourceRepo { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the changelog to be shown when obtaining the testing version of the plugin.
|
||||
/// </summary>
|
||||
public string? TestingChangelog { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin is eligible for testing.
|
||||
|
|
|
|||
|
|
@ -166,7 +166,12 @@ internal class CallGateChannel
|
|||
if (arg == null)
|
||||
{
|
||||
if (paramType.IsValueType)
|
||||
{
|
||||
if (paramType.IsGenericType && paramType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
continue;
|
||||
|
||||
throw new IpcValueNullError(this.Name, paramType, i);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Dalamud.Plugin.Ipc.Internal;
|
|||
/// <summary>
|
||||
/// This class facilitates sharing data-references of standard types between plugins without using more expensive IPC.
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class DataShare : IServiceType
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,11 @@ public interface IClientState
|
|||
/// Gets the current Territory the player resides in.
|
||||
/// </summary>
|
||||
public ushort TerritoryType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Map the player resides in.
|
||||
/// </summary>
|
||||
public uint MapId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local player character, if one is present.
|
||||
|
|
|
|||
38
Dalamud/Plugin/Services/IContextMenu.cs
Normal file
38
Dalamud/Plugin/Services/IContextMenu.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using Dalamud.Game.Gui.ContextMenu;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides methods for interacting with the game's context menu.
|
||||
/// </summary>
|
||||
public interface IContextMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate type used for the <see cref="OnMenuOpened"/> event.
|
||||
/// </summary>
|
||||
/// <param name="args">Information about the currently opening menu.</param>
|
||||
public delegate void OnMenuOpenedDelegate(MenuOpenedArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired whenever any context menu is opened.
|
||||
/// </summary>
|
||||
/// <remarks>Use this event and then check if the triggering addon is the desired addon, then add custom context menu items to the provided args.</remarks>
|
||||
event OnMenuOpenedDelegate OnMenuOpened;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a menu item to a context menu.
|
||||
/// </summary>
|
||||
/// <param name="menuType">The type of context menu to add the item to.</param>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <remarks>Used to add a context menu entry to <em>all</em> context menus.</remarks>
|
||||
void AddMenuItem(ContextMenuType menuType, MenuItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a menu item from a context menu.
|
||||
/// </summary>
|
||||
/// <param name="menuType">The type of context menu to remove the item from.</param>
|
||||
/// <param name="item">The item to add.</param>
|
||||
/// <remarks>Used to remove a context menu entry from <em>all</em> context menus.</remarks>
|
||||
/// <returns><see langword="true"/> if the item was removed, <see langword="false"/> if it was not found.</returns>
|
||||
bool RemoveMenuItem(ContextMenuType menuType, MenuItem item);
|
||||
}
|
||||
|
|
@ -1,11 +1,29 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the Framework of the native game client and grants access to various subsystems.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Choosing between <c>RunOnFrameworkThread</c> and <c>Run</c></b></para>
|
||||
/// <ul>
|
||||
/// <li>If you do need to do use <c>await</c> and have your task keep executing on the main thread after waiting is
|
||||
/// done, use <c>Run</c>.</li>
|
||||
/// <li>If you need to call <see cref="Task.Wait()"/> or <see cref="Task{TResult}.Result"/>, use
|
||||
/// <c>RunOnFrameworkThread</c>. It also skips the task scheduler if invoked already from the framework thread.</li>
|
||||
/// </ul>
|
||||
/// <para>The game is likely to completely lock up if you call above synchronous function and getter, because starting
|
||||
/// a new task by default runs on <see cref="TaskScheduler.Current"/>, which would make the task run on the framework
|
||||
/// thread if invoked via <c>Run</c>. This includes <c>Task.Factory.StartNew</c> and
|
||||
/// <c>Task.ContinueWith</c>. Use <c>Task.Run</c> if you need to start a new task from the callback specified to
|
||||
/// <c>Run</c>, as it will force your task to be run in the default thread pool.</para>
|
||||
/// <para>See <see cref="TaskSchedulerWidget"/> to see the difference in behaviors, and how would a misuse of these
|
||||
/// functions result in a deadlock.</para>
|
||||
/// </remarks>
|
||||
public interface IFramework
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -44,12 +62,97 @@ public interface IFramework
|
|||
/// </summary>
|
||||
public bool IsFrameworkUnloading { get; }
|
||||
|
||||
/// <summary>Gets a <see cref="TaskFactory"/> that runs tasks during Framework Update event.</summary>
|
||||
/// <returns>The task factory.</returns>
|
||||
public TaskFactory GetTaskFactory();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a task that completes after the given number of ticks.
|
||||
/// </summary>
|
||||
/// <param name="numTicks">Number of ticks to delay.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A new <see cref="Task"/> that gets resolved after specified number of ticks happen.</returns>
|
||||
/// <remarks>The continuation will run on the framework thread by default.</remarks>
|
||||
public Task DelayTicks(long numTicks, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <param name="action">Function to call.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Starting new tasks and waiting on them <b>synchronously</b> from this callback will completely lock up
|
||||
/// the game. Use <c>await</c> if you need to wait on something from an <c>async</c> callback.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
public Task Run(Action action, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="action">Function to call.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Starting new tasks and waiting on them <b>synchronously</b> from this callback will completely lock up
|
||||
/// the game. Use <c>await</c> if you need to wait on something from an <c>async</c> callback.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
public Task<T> Run<T>(Func<T> action, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <param name="action">Function to call.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Starting new tasks and waiting on them <b>synchronously</b> from this callback will completely lock up
|
||||
/// the game. Use <c>await</c> if you need to wait on something from an <c>async</c> callback.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
public Task Run(Func<Task> action, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="action">Function to call.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Starting new tasks and waiting on them <b>synchronously</b> from this callback will completely lock up
|
||||
/// the game. Use <c>await</c> if you need to wait on something from an <c>async</c> callback.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
public Task<T> Run<T>(Func<Task<T>> action, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <remarks>
|
||||
/// <para><c>await</c>, <c>Task.Factory.StartNew</c> or alike will continue off the framework thread.</para>
|
||||
/// <para>Awaiting on the returned <see cref="Task"/> from <c>RunOnFrameworkThread</c>,
|
||||
/// <c>Run</c>, or <c>RunOnTick</c> right away inside the callback specified to this
|
||||
/// function has a chance of locking up the game. Do not do <c>await framework.RunOnFrameworkThread(...);</c>
|
||||
/// directly or indirectly from the delegate passed to this function.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
public Task<T> RunOnFrameworkThread<T>(Func<T> func);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -57,6 +160,16 @@ public interface IFramework
|
|||
/// </summary>
|
||||
/// <param name="action">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <remarks>
|
||||
/// <para><c>await</c>, <c>Task.Factory.StartNew</c> or alike will continue off the framework thread.</para>
|
||||
/// <para>Awaiting on the returned <see cref="Task"/> from <c>RunOnFrameworkThread</c>,
|
||||
/// <c>Run</c>, or <c>RunOnTick</c> right away inside the callback specified to this
|
||||
/// function has a chance of locking up the game. Do not do <c>await framework.RunOnFrameworkThread(...);</c>
|
||||
/// directly or indirectly from the delegate passed to this function.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
public Task RunOnFrameworkThread(Action action);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -65,6 +178,17 @@ public interface IFramework
|
|||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <remarks>
|
||||
/// <para><c>await</c>, <c>Task.Factory.StartNew</c> or alike will continue off the framework thread.</para>
|
||||
/// <para>Awaiting on the returned <see cref="Task"/> from <c>RunOnFrameworkThread</c>,
|
||||
/// <c>Run</c>, or <c>RunOnTick</c> right away inside the callback specified to this
|
||||
/// function has a chance of locking up the game. Do not do <c>await framework.RunOnFrameworkThread(...);</c>
|
||||
/// directly or indirectly from the delegate passed to this function.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
[Obsolete($"Use {nameof(RunOnTick)} instead.")]
|
||||
public Task<T> RunOnFrameworkThread<T>(Func<Task<T>> func);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -72,6 +196,17 @@ public interface IFramework
|
|||
/// </summary>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
/// <remarks>
|
||||
/// <para><c>await</c>, <c>Task.Factory.StartNew</c> or alike will continue off the framework thread.</para>
|
||||
/// <para>Awaiting on the returned <see cref="Task"/> from <c>RunOnFrameworkThread</c>,
|
||||
/// <c>Run</c>, or <c>RunOnTick</c> right away inside the callback specified to this
|
||||
/// function has a chance of locking up the game. Do not do <c>await framework.RunOnFrameworkThread(...);</c>
|
||||
/// directly or indirectly from the delegate passed to this function.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
[Obsolete($"Use {nameof(RunOnTick)} instead.")]
|
||||
public Task RunOnFrameworkThread(Func<Task> func);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -83,6 +218,16 @@ public interface IFramework
|
|||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
/// <remarks>
|
||||
/// <para><c>await</c>, <c>Task.Factory.StartNew</c> or alike will continue off the framework thread.</para>
|
||||
/// <para>Awaiting on the returned <see cref="Task"/> from <c>RunOnFrameworkThread</c>,
|
||||
/// <c>Run</c>, or <c>RunOnTick</c> right away inside the callback specified to this
|
||||
/// function has a chance of locking up the game. Do not do <c>await framework.RunOnFrameworkThread(...);</c>
|
||||
/// directly or indirectly from the delegate passed to this function.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
public Task<T> RunOnTick<T>(Func<T> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -93,6 +238,16 @@ public interface IFramework
|
|||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
/// <remarks>
|
||||
/// <para><c>await</c>, <c>Task.Factory.StartNew</c> or alike will continue off the framework thread.</para>
|
||||
/// <para>Awaiting on the returned <see cref="Task"/> from <c>RunOnFrameworkThread</c>,
|
||||
/// <c>Run</c>, or <c>RunOnTick</c> right away inside the callback specified to this
|
||||
/// function has a chance of locking up the game. Do not do <c>await framework.RunOnFrameworkThread(...);</c>
|
||||
/// directly or indirectly from the delegate passed to this function.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
public Task RunOnTick(Action action, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -104,6 +259,16 @@ public interface IFramework
|
|||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
/// <remarks>
|
||||
/// <para><c>await</c>, <c>Task.Factory.StartNew</c> or alike will continue off the framework thread.</para>
|
||||
/// <para>Awaiting on the returned <see cref="Task"/> from <c>RunOnFrameworkThread</c>,
|
||||
/// <c>Run</c>, or <c>RunOnTick</c> right away inside the callback specified to this
|
||||
/// function has a chance of locking up the game. Do not do <c>await framework.RunOnFrameworkThread(...);</c>
|
||||
/// directly or indirectly from the delegate passed to this function.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
public Task<T> RunOnTick<T>(Func<Task<T>> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -114,5 +279,15 @@ public interface IFramework
|
|||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
/// <remarks>
|
||||
/// <para><c>await</c>, <c>Task.Factory.StartNew</c> or alike will continue off the framework thread.</para>
|
||||
/// <para>Awaiting on the returned <see cref="Task"/> from <c>RunOnFrameworkThread</c>,
|
||||
/// <c>Run</c>, or <c>RunOnTick</c> right away inside the callback specified to this
|
||||
/// function has a chance of locking up the game. Do not do <c>await framework.RunOnFrameworkThread(...);</c>
|
||||
/// directly or indirectly from the delegate passed to this function.</para>
|
||||
/// <para>See the remarks on <see cref="IFramework"/> if you need to choose which one to use, between
|
||||
/// <c>Run</c> and <c>RunOnFrameworkThread</c>. Note that <c>RunOnTick</c> is a fancy
|
||||
/// version of <c>RunOnFrameworkThread</c>.</para>
|
||||
/// </remarks>
|
||||
public Task RunOnTick(Func<Task> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
using System.Text;
|
||||
|
||||
using Dalamud.Game.Libc;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// This class handles creating cstrings utilizing native game methods.
|
||||
/// </summary>
|
||||
public interface ILibcFunction
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new string from the given bytes.
|
||||
/// </summary>
|
||||
/// <param name="content">The bytes to convert.</param>
|
||||
/// <returns>An owned std string object.</returns>
|
||||
public OwnedStdString NewString(byte[] content);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new string form the given bytes.
|
||||
/// </summary>
|
||||
/// <param name="content">The bytes to convert.</param>
|
||||
/// <param name="encoding">A non-default encoding.</param>
|
||||
/// <returns>An owned std string object.</returns>
|
||||
public OwnedStdString NewString(string content, Encoding? encoding = null);
|
||||
}
|
||||
12
Dalamud/Plugin/Services/INotificationManager.cs
Normal file
12
Dalamud/Plugin/Services/INotificationManager.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using Dalamud.Interface.ImGuiNotification;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>Manager for notifications provided by Dalamud using ImGui.</summary>
|
||||
public interface INotificationManager
|
||||
{
|
||||
/// <summary>Adds a notification.</summary>
|
||||
/// <param name="notification">The new notification.</param>
|
||||
/// <returns>The added notification.</returns>
|
||||
IActiveNotification AddNotification(Notification notification);
|
||||
}
|
||||
|
|
@ -7,18 +7,18 @@ namespace Dalamud.Plugin.Services;
|
|||
/// <summary>
|
||||
/// This collection represents the currently spawned FFXIV game objects.
|
||||
/// </summary>
|
||||
public interface IObjectTable : IReadOnlyCollection<GameObject>
|
||||
public interface IObjectTable : IEnumerable<GameObject>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the object table.
|
||||
/// </summary>
|
||||
public nint Address { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the object table.
|
||||
/// </summary>
|
||||
public int Length { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get an object at the specified spawn index.
|
||||
/// </summary>
|
||||
|
|
@ -32,14 +32,14 @@ public interface IObjectTable : IReadOnlyCollection<GameObject>
|
|||
/// <param name="objectId">Object ID to find.</param>
|
||||
/// <returns>A game object or null.</returns>
|
||||
public GameObject? SearchById(ulong objectId);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the game object at the specified index of the object table.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the object.</param>
|
||||
/// <returns>The memory address of the object.</returns>
|
||||
public nint GetObjectAddress(int index);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a reference to an FFXIV game object.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
using System.IO;
|
||||
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Textures;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Data.Files;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service that grants you access to textures you may render via ImGui.
|
||||
/// </summary>
|
||||
public partial interface ITextureProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags describing the icon you wish to receive.
|
||||
/// </summary>
|
||||
[Obsolete($"Use {nameof(GameIconLookup)}.")]
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
[Flags]
|
||||
public enum IconFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Low-resolution, standard quality icon.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// If this icon is an item icon, and it has a high-quality variant, receive the high-quality version.
|
||||
/// Null if the item does not have a high-quality variant.
|
||||
/// </summary>
|
||||
ItemHighQuality = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Get the hi-resolution version of the icon, if it exists.
|
||||
/// </summary>
|
||||
HiRes = 1 << 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for a specific icon.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The ID of the icon to load.</param>
|
||||
/// <param name="flags">Options to be considered when loading the icon.</param>
|
||||
/// <param name="language">
|
||||
/// The language to be considered when loading the icon, if the icon has versions for multiple languages.
|
||||
/// If null, default to the game's current language.
|
||||
/// </param>
|
||||
/// <param name="keepAlive">
|
||||
/// Not used. This parameter is ignored.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Null, if the icon does not exist in the specified configuration, or a texture wrap that can be used
|
||||
/// to render the icon.
|
||||
/// </returns>
|
||||
[Obsolete($"Use {nameof(GetFromGameIcon)}.")]
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
public IDalamudTextureWrap? GetIcon(uint iconId, IconFlags flags = IconFlags.HiRes, ClientLanguage? language = null, bool keepAlive = false);
|
||||
|
||||
/// <summary>
|
||||
/// Get a path for a specific icon's .tex file.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The ID of the icon to look up.</param>
|
||||
/// <param name="flags">Options to be considered when loading the icon.</param>
|
||||
/// <param name="language">
|
||||
/// The language to be considered when loading the icon, if the icon has versions for multiple languages.
|
||||
/// If null, default to the game's current language.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Null, if the icon does not exist in the specified configuration, or the path to the texture's .tex file,
|
||||
/// which can be loaded via IDataManager.
|
||||
/// </returns>
|
||||
[Obsolete($"Use {nameof(TryGetIconPath)} or {nameof(GetIconPath)}({nameof(GameIconLookup)}).")]
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
public string? GetIconPath(uint iconId, IconFlags flags = IconFlags.HiRes, ClientLanguage? language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for the texture at the specified path.
|
||||
/// You may only specify paths in the game's VFS.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the texture in the game's VFS.</param>
|
||||
/// <param name="keepAlive">Not used. This parameter is ignored.</param>
|
||||
/// <returns>Null, if the icon does not exist, or a texture wrap that can be used to render the texture.</returns>
|
||||
[Obsolete($"Use {nameof(GetFromGame)}.")]
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
public IDalamudTextureWrap? GetTextureFromGame(string path, bool keepAlive = false);
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for the image or texture, specified by the passed FileInfo.
|
||||
/// You may only specify paths on the native file system.
|
||||
///
|
||||
/// This API can load .png and .tex files.
|
||||
/// </summary>
|
||||
/// <param name="file">The FileInfo describing the image or texture file.</param>
|
||||
/// <param name="keepAlive">Not used. This parameter is ignored.</param>
|
||||
/// <returns>Null, if the file does not exist, or a texture wrap that can be used to render the texture.</returns>
|
||||
[Obsolete($"Use {nameof(GetFromFile)}.")]
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
public IDalamudTextureWrap? GetTextureFromFile(FileInfo file, bool keepAlive = false);
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for the specified Lumina <see cref="TexFile"/>.
|
||||
/// </summary>
|
||||
/// <param name="file">The texture to obtain a handle to.</param>
|
||||
/// <returns>A texture wrap that can be used to render the texture. Dispose after use.</returns>
|
||||
[Obsolete($"Use {nameof(CreateFromTexFile)}.")]
|
||||
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
|
||||
IDalamudTextureWrap GetTexture(TexFile file) => this.CreateFromTexFile(file);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue