Merge branch 'apiX' into feature/itextureprovider-updates

This commit is contained in:
Soreepeong 2024-05-12 22:17:32 +09:00
commit 8c7771bf7d
2213 changed files with 10372 additions and 1088868 deletions

View file

@ -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>

View file

@ -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);
}
}
*/

View 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.";
}
}

View file

@ -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.

View file

@ -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;
}

View file

@ -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");

View file

@ -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()

View file

@ -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.

View file

@ -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.

View file

@ -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;
}

View file

@ -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>

View file

@ -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.

View 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);
}

View file

@ -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);
}

View file

@ -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);
}

View 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);
}

View file

@ -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>

View file

@ -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);
}