Merge pull request #1757 from goatcorp/new_im_hooks-rollup

[new_im_hooks] Rollup changes from master
This commit is contained in:
goat 2024-04-10 00:46:24 +02:00 committed by GitHub
commit 0cf7f2f881
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 248 additions and 246 deletions

View file

@ -20,7 +20,7 @@ namespace Dalamud.Data;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IDataManager>] [ResolveVia<IDataManager>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using CheapLoc; using CheapLoc;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Gui; using Dalamud.Game.Gui;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
@ -25,7 +26,7 @@ namespace Dalamud.Game;
/// <summary> /// <summary>
/// Chat events and public helper functions. /// Chat events and public helper functions.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class ChatHandlers : IServiceType internal class ChatHandlers : IServiceType
{ {
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new() // private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
@ -290,13 +291,20 @@ internal class ChatHandlers : IServiceType
var chatGui = Service<ChatGui>.GetNullable(); var chatGui = Service<ChatGui>.GetNullable();
var pluginManager = Service<PluginManager>.GetNullable(); var pluginManager = Service<PluginManager>.GetNullable();
var notifications = Service<NotificationManager>.GetNullable(); var notifications = Service<NotificationManager>.GetNullable();
var condition = Service<Condition>.GetNullable();
if (chatGui == null || pluginManager == null || notifications == null) if (chatGui == null || pluginManager == null || notifications == null || condition == null)
{ {
Log.Warning("Aborting auto-update because a required service was not loaded."); Log.Warning("Aborting auto-update because a required service was not loaded.");
return false; return false;
} }
if (condition.Any(ConditionFlag.BoundByDuty, ConditionFlag.BoundByDuty56, ConditionFlag.BoundByDuty95))
{
Log.Warning("Aborting auto-update because the player is in a duty.");
return false;
}
if (!pluginManager.ReposReady || !pluginManager.InstalledPlugins.Any() || !pluginManager.AvailablePlugins.Any()) if (!pluginManager.ReposReady || !pluginManager.InstalledPlugins.Any() || !pluginManager.AvailablePlugins.Any())
{ {
// Plugins aren't ready yet. // Plugins aren't ready yet.

View file

@ -14,7 +14,7 @@ namespace Dalamud.Game.ClientState.Aetherytes;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IAetheryteList>] [ResolveVia<IAetheryteList>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -16,7 +16,7 @@ namespace Dalamud.Game.ClientState.Buddy;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IBuddyList>] [ResolveVia<IBuddyList>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -22,7 +22,7 @@ namespace Dalamud.Game.ClientState;
/// This class represents the state of the game client at the time of access. /// This class represents the state of the game client at the time of access.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class ClientState : IInternalDisposableService, IClientState internal sealed class ClientState : IInternalDisposableService, IClientState
{ {
private static readonly ModuleLog Log = new("ClientState"); private static readonly ModuleLog Log = new("ClientState");

View file

@ -9,8 +9,8 @@ namespace Dalamud.Game.ClientState.Conditions;
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc. /// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed partial class Condition : IInternalDisposableService, ICondition internal sealed class Condition : IInternalDisposableService, ICondition
{ {
/// <summary> /// <summary>
/// Gets the current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has. /// Gets the current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.

View file

@ -428,7 +428,14 @@ public enum ConditionFlag
/// <summary> /// <summary>
/// Unable to execute command while bound by duty. /// Unable to execute command while bound by duty.
/// </summary> /// </summary>
[Obsolete("Use InDutyQueue")]
BoundToDuty97 = 91, BoundToDuty97 = 91,
/// <summary>
/// Unable to execute command while bound by duty.
/// Specifically triggered when you are in a queue for a duty but not inside a duty.
/// </summary>
InDutyQueue = 91,
/// <summary> /// <summary>
/// Unable to execute command while readying to visit another World. /// Unable to execute command while readying to visit another World.

View file

@ -14,7 +14,7 @@ namespace Dalamud.Game.ClientState.Fates;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IFateTable>] [ResolveVia<IFateTable>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -17,7 +17,7 @@ namespace Dalamud.Game.ClientState.GamePad;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IGamepadState>] [ResolveVia<IGamepadState>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.ClientState.JobGauge;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IJobGauges>] [ResolveVia<IJobGauges>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -24,7 +24,7 @@ namespace Dalamud.Game.ClientState.Keys;
/// </remarks> /// </remarks>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IKeyState>] [ResolveVia<IKeyState>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -24,7 +24,7 @@ namespace Dalamud.Game.ClientState.Objects;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IObjectTable>] [ResolveVia<IObjectTable>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -12,7 +12,7 @@ namespace Dalamud.Game.ClientState.Objects;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<ITargetManager>] [ResolveVia<ITargetManager>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.ClientState.Party;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IPartyList>] [ResolveVia<IPartyList>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -18,7 +18,7 @@ namespace Dalamud.Game.Command;
/// This class manages registered in-game slash commands. /// This class manages registered in-game slash commands.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class CommandManager : IInternalDisposableService, ICommandManager internal sealed class CommandManager : IInternalDisposableService, ICommandManager
{ {
private static readonly ModuleLog Log = new("Command"); private static readonly ModuleLog Log = new("Command");

View file

@ -14,7 +14,7 @@ namespace Dalamud.Game.Config;
/// This class represents the game's configuration. /// This class represents the game's configuration.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class GameConfig : IInternalDisposableService, IGameConfig internal sealed class GameConfig : IInternalDisposableService, IGameConfig
{ {
private readonly TaskCompletionSource tcsInitialization = new(); private readonly TaskCompletionSource tcsInitialization = new();

View file

@ -12,7 +12,7 @@ namespace Dalamud.Game.DutyState;
/// This class represents the state of the currently occupied duty. /// This class represents the state of the currently occupied duty.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal unsafe class DutyState : IInternalDisposableService, IDutyState internal unsafe class DutyState : IInternalDisposableService, IDutyState
{ {
private readonly DutyStateAddressResolver address; private readonly DutyStateAddressResolver address;

View file

@ -22,15 +22,13 @@ namespace Dalamud.Game;
/// This class represents the Framework of the native game client and grants access to various subsystems. /// This class represents the Framework of the native game client and grants access to various subsystems.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class Framework : IInternalDisposableService, IFramework internal sealed class Framework : IInternalDisposableService, IFramework
{ {
private static readonly ModuleLog Log = new("Framework"); private static readonly ModuleLog Log = new("Framework");
private static readonly Stopwatch StatsStopwatch = new(); private static readonly Stopwatch StatsStopwatch = new();
private readonly GameLifecycle lifecycle;
private readonly Stopwatch updateStopwatch = new(); private readonly Stopwatch updateStopwatch = new();
private readonly HitchDetector hitchDetector; private readonly HitchDetector hitchDetector;
@ -38,6 +36,9 @@ internal sealed class Framework : IInternalDisposableService, IFramework
private readonly Hook<OnRealDestroyDelegate> destroyHook; private readonly Hook<OnRealDestroyDelegate> destroyHook;
private readonly FrameworkAddressResolver addressResolver; private readonly FrameworkAddressResolver addressResolver;
[ServiceManager.ServiceDependency]
private readonly GameLifecycle lifecycle = Service<GameLifecycle>.Get();
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get(); private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
@ -51,9 +52,8 @@ internal sealed class Framework : IInternalDisposableService, IFramework
private ulong tickCounter; private ulong tickCounter;
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private Framework(TargetSigScanner sigScanner, GameLifecycle lifecycle) private Framework(TargetSigScanner sigScanner)
{ {
this.lifecycle = lifecycle;
this.hitchDetector = new HitchDetector("FrameworkUpdate", this.configuration.FrameworkUpdateHitch); this.hitchDetector = new HitchDetector("FrameworkUpdate", this.configuration.FrameworkUpdateHitch);
this.addressResolver = new FrameworkAddressResolver(); this.addressResolver = new FrameworkAddressResolver();
@ -90,7 +90,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework
/// <summary> /// <summary>
/// Executes during FrameworkUpdate before all <see cref="Update"/> delegates. /// Executes during FrameworkUpdate before all <see cref="Update"/> delegates.
/// </summary> /// </summary>
internal event IFramework.OnUpdateDelegate BeforeUpdate; internal event IFramework.OnUpdateDelegate? BeforeUpdate;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the collection of stats is enabled. /// Gets or sets a value indicating whether the collection of stats is enabled.

View file

@ -11,7 +11,7 @@ namespace Dalamud.Game;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IGameLifecycle>] [ResolveVia<IGameLifecycle>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -12,6 +12,7 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal; using Dalamud.Logging.Internal;
using Dalamud.Memory; using Dalamud.Memory;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.System.String;
@ -28,7 +29,7 @@ namespace Dalamud.Game.Gui;
/// This class handles interacting with the native chat UI. /// This class handles interacting with the native chat UI.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
{ {
private static readonly ModuleLog Log = new("ChatGui"); private static readonly ModuleLog Log = new("ChatGui");
@ -61,7 +62,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
this.populateItemLinkHook.Enable(); this.populateItemLinkHook.Enable();
this.interactableLinkClickedHook.Enable(); this.interactableLinkClickedHook.Enable();
} }
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate uint PrintMessageDelegate(RaptureLogModule* manager, XivChatType chatType, Utf8String* sender, Utf8String* message, int timestamp, byte silent); private delegate uint PrintMessageDelegate(RaptureLogModule* manager, XivChatType chatType, Utf8String* sender, Utf8String* message, int timestamp, byte silent);
@ -121,31 +122,31 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
{ {
this.chatQueue.Enqueue(chat); this.chatQueue.Enqueue(chat);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Print(string message, string? messageTag = null, ushort? tagColor = null) public void Print(string message, string? messageTag = null, ushort? tagColor = null)
{ {
this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor); this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Print(SeString message, string? messageTag = null, ushort? tagColor = null) public void Print(SeString message, string? messageTag = null, ushort? tagColor = null)
{ {
this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor); this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void PrintError(string message, string? messageTag = null, ushort? tagColor = null) public void PrintError(string message, string? messageTag = null, ushort? tagColor = null)
{ {
this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor); this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null) public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null)
{ {
this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor); this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor);
} }
/// <summary> /// <summary>
/// Process a chat queue. /// Process a chat queue.
/// </summary> /// </summary>
@ -154,9 +155,29 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
while (this.chatQueue.Count > 0) while (this.chatQueue.Count > 0)
{ {
var chat = this.chatQueue.Dequeue(); var chat = this.chatQueue.Dequeue();
var replacedMessage = new SeStringBuilder();
// Normalize Unicode NBSP to the built-in one, as the former won't renderl
foreach (var payload in chat.Message.Payloads)
{
if (payload is TextPayload { Text: not null } textPayload)
{
var split = textPayload.Text.Split("\u202f"); // NARROW NO-BREAK SPACE
for (var i = 0; i < split.Length; i++)
{
replacedMessage.AddText(split[i]);
if (i + 1 < split.Length)
replacedMessage.Add(new RawPayload([0x02, (byte)Lumina.Text.Payloads.PayloadType.Indent, 0x01, 0x03]));
}
}
else
{
replacedMessage.Add(payload);
}
}
var sender = Utf8String.FromSequence(chat.Name.Encode()); var sender = Utf8String.FromSequence(chat.Name.Encode());
var message = Utf8String.FromSequence(chat.Message.Encode()); var message = Utf8String.FromSequence(replacedMessage.BuiltString.Encode());
this.HandlePrintMessageDetour(RaptureLogModule.Instance(), chat.Type, sender, message, (int)chat.SenderId, (byte)(chat.Parameters != 0 ? 1 : 0)); this.HandlePrintMessageDetour(RaptureLogModule.Instance(), chat.Type, sender, message, (int)chat.SenderId, (byte)(chat.Parameters != 0 ? 1 : 0));
@ -193,7 +214,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
lock (this.dalamudLinkHandlers) lock (this.dalamudLinkHandlers)
{ {
var changed = false; var changed = false;
foreach (var handler in this.RegisteredLinkHandlers.Keys.Where(k => k.PluginName == pluginName)) foreach (var handler in this.RegisteredLinkHandlers.Keys.Where(k => k.PluginName == pluginName))
changed |= this.dalamudLinkHandlers.Remove(handler); changed |= this.dalamudLinkHandlers.Remove(handler);
if (changed) if (changed)
@ -230,18 +251,18 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
builder.AddText($"[{tag}] "); builder.AddText($"[{tag}] ");
} }
} }
this.Print(new XivChatEntry this.Print(new XivChatEntry
{ {
Message = builder.AddText(message).Build(), Message = builder.AddText(message).Build(),
Type = channel, Type = channel,
}); });
} }
private void PrintTagged(SeString message, XivChatType channel, string? tag, ushort? color) private void PrintTagged(SeString message, XivChatType channel, string? tag, ushort? color)
{ {
var builder = new SeStringBuilder(); var builder = new SeStringBuilder();
if (!tag.IsNullOrEmpty()) if (!tag.IsNullOrEmpty())
{ {
if (color is not null) if (color is not null)
@ -253,7 +274,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
builder.AddText($"[{tag}] "); builder.AddText($"[{tag}] ");
} }
} }
this.Print(new XivChatEntry this.Print(new XivChatEntry
{ {
Message = builder.Build().Append(message), Message = builder.Build().Append(message),
@ -424,16 +445,16 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
this.chatGuiService.ChatMessageHandled += this.OnMessageHandledForward; this.chatGuiService.ChatMessageHandled += this.OnMessageHandledForward;
this.chatGuiService.ChatMessageUnhandled += this.OnMessageUnhandledForward; this.chatGuiService.ChatMessageUnhandled += this.OnMessageUnhandledForward;
} }
/// <inheritdoc/> /// <inheritdoc/>
public event IChatGui.OnMessageDelegate? ChatMessage; public event IChatGui.OnMessageDelegate? ChatMessage;
/// <inheritdoc/> /// <inheritdoc/>
public event IChatGui.OnCheckMessageHandledDelegate? CheckMessageHandled; public event IChatGui.OnCheckMessageHandledDelegate? CheckMessageHandled;
/// <inheritdoc/> /// <inheritdoc/>
public event IChatGui.OnMessageHandledDelegate? ChatMessageHandled; public event IChatGui.OnMessageHandledDelegate? ChatMessageHandled;
/// <inheritdoc/> /// <inheritdoc/>
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled; public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
@ -459,23 +480,23 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
this.ChatMessageHandled = null; this.ChatMessageHandled = null;
this.ChatMessageUnhandled = null; this.ChatMessageUnhandled = null;
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Print(XivChatEntry chat) public void Print(XivChatEntry chat)
=> this.chatGuiService.Print(chat); => this.chatGuiService.Print(chat);
/// <inheritdoc/> /// <inheritdoc/>
public void Print(string message, string? messageTag = null, ushort? tagColor = null) public void Print(string message, string? messageTag = null, ushort? tagColor = null)
=> this.chatGuiService.Print(message, messageTag, tagColor); => this.chatGuiService.Print(message, messageTag, tagColor);
/// <inheritdoc/> /// <inheritdoc/>
public void Print(SeString message, string? messageTag = null, ushort? tagColor = null) public void Print(SeString message, string? messageTag = null, ushort? tagColor = null)
=> this.chatGuiService.Print(message, messageTag, tagColor); => this.chatGuiService.Print(message, messageTag, tagColor);
/// <inheritdoc/> /// <inheritdoc/>
public void PrintError(string message, string? messageTag = null, ushort? tagColor = null) public void PrintError(string message, string? messageTag = null, ushort? tagColor = null)
=> this.chatGuiService.PrintError(message, messageTag, tagColor); => this.chatGuiService.PrintError(message, messageTag, tagColor);
/// <inheritdoc/> /// <inheritdoc/>
public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null) public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null)
=> this.chatGuiService.PrintError(message, messageTag, tagColor); => this.chatGuiService.PrintError(message, messageTag, tagColor);

View file

@ -21,7 +21,7 @@ namespace Dalamud.Game.Gui.Dtr;
/// Class used to interface with the server info bar. /// Class used to interface with the server info bar.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
{ {
private const uint BaseNodeId = 1000; private const uint BaseNodeId = 1000;

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.Gui.FlyText;
/// This class facilitates interacting with and creating native in-game "fly text". /// This class facilitates interacting with and creating native in-game "fly text".
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
{ {
/// <summary> /// <summary>

View file

@ -26,7 +26,7 @@ namespace Dalamud.Game.Gui;
/// A class handling many aspects of the in-game UI. /// A class handling many aspects of the in-game UI.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
{ {
private static readonly ModuleLog Log = new("GameGui"); private static readonly ModuleLog Log = new("GameGui");

View file

@ -14,7 +14,7 @@ namespace Dalamud.Game.Gui.PartyFinder;
/// This class handles interacting with the native PartyFinder window. /// This class handles interacting with the native PartyFinder window.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderGui internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderGui
{ {
private readonly PartyFinderAddressResolver address; private readonly PartyFinderAddressResolver address;

View file

@ -13,7 +13,7 @@ namespace Dalamud.Game.Gui.Toast;
/// This class facilitates interacting with and creating native toast windows. /// This class facilitates interacting with and creating native toast windows.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed partial class ToastGui : IInternalDisposableService, IToastGui internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
{ {
private const uint QuestToastCheckmarkMagic = 60081; private const uint QuestToastCheckmarkMagic = 60081;

View file

@ -18,7 +18,7 @@ namespace Dalamud.Game.Inventory;
/// This class provides events for the players in-game inventory. /// This class provides events for the players in-game inventory.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class GameInventory : IInternalDisposableService internal class GameInventory : IInternalDisposableService
{ {
private readonly List<GameInventoryPluginScoped> subscribersPendingChange = new(); private readonly List<GameInventoryPluginScoped> subscribersPendingChange = new();

View file

@ -13,7 +13,7 @@ namespace Dalamud.Game.Libc;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<ILibcFunction>] [ResolveVia<ILibcFunction>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -14,7 +14,7 @@ namespace Dalamud.Game.Network;
/// This class handles interacting with game network events. /// This class handles interacting with game network events.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
{ {
private readonly GameNetworkAddressResolver address; private readonly GameNetworkAddressResolver address;

View file

@ -25,7 +25,7 @@ namespace Dalamud.Game.Network.Internal;
/// <summary> /// <summary>
/// This class handles network notifications and uploading market board data. /// This class handles network notifications and uploading market board data.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal unsafe class NetworkHandlers : IInternalDisposableService internal unsafe class NetworkHandlers : IInternalDisposableService
{ {
private readonly IMarketBoardUploader uploader; private readonly IMarketBoardUploader uploader;

View file

@ -14,7 +14,7 @@ namespace Dalamud.Hooking.WndProcHook;
/// <summary> /// <summary>
/// Manages WndProc hooks for game main window and extra ImGui viewport windows. /// Manages WndProc hooks for game main window and extra ImGui viewport windows.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class WndProcHookManager : IInternalDisposableService internal sealed class WndProcHookManager : IInternalDisposableService
{ {
private static readonly ModuleLog Log = new(nameof(WndProcHookManager)); private static readonly ModuleLog Log = new(nameof(WndProcHookManager));

View file

@ -15,7 +15,7 @@ namespace Dalamud.Interface.DragDrop;
/// and can be used to create ImGui drag and drop sources and targets for those external events. /// and can be used to create ImGui drag and drop sources and targets for those external events.
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IDragDropManager>] [ResolveVia<IDragDropManager>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -1,96 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal.Windows;
using Dalamud.Networking.Http;
using Serilog;
namespace Dalamud.Interface;
[ServiceManager.EarlyLoadedService]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "One-off")]
internal class Fools24 : IServiceType
{
private readonly HappyHttpClient httpClient;
private CancellationTokenSource? cancellation;
private Task? horseIconTask = null;
private string[]? applicableIcons = null;
[ServiceManager.ServiceConstructor]
public Fools24(HappyHttpClient httpClient)
{
this.httpClient = httpClient;
}
public bool IsWaitingForIconList => this.horseIconTask?.IsCompleted == false;
public bool Failed { get; private set; }
public static bool IsDayApplicable()
{
var utcNow = DateTime.UtcNow;
var dateAhead = utcNow.AddHours(14);
var dateBehind = utcNow.AddHours(-12);
return dateAhead is { Day: 1, Month: 4 } || dateBehind is { Day: 1, Month: 4 } || DateTime.Now is { Day: 1, Month: 4 };
}
public string? GetHorseIconLink(string internalName)
{
if (this.applicableIcons == null || this.applicableIcons.All(x => x != $"{internalName}.png"))
return null;
return $"https://raw.githubusercontent.com/goaaats/horse-icons/main/icons/{internalName}.png";
}
public void NotifyInstallerWindowOpened()
{
if (!IsDayApplicable())
return;
Service<PluginImageCache>.Get().ClearIconCache();
if (this.horseIconTask?.IsCompleted == false)
return;
this.Failed = false;
try
{
this.cancellation = new CancellationTokenSource();
this.horseIconTask = this.httpClient.SharedHttpClient.GetStringAsync("https://raw.githubusercontent.com/goaaats/horse-icons/main/iconlist.txt", this.cancellation.Token)
.ContinueWith(
f =>
{
if (!f.IsCompletedSuccessfully)
{
this.Failed = true;
this.applicableIcons = null;
return;
}
if (f.Result is not { Length: > 0 })
{
this.Failed = true;
this.applicableIcons = null;
return;
}
this.applicableIcons = f.Result.Split(
'\n',
StringSplitOptions.RemoveEmptyEntries);
});
this.cancellation.CancelAfter(10000);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to fetch horse icons");
this.Failed = true;
}
}
}

View file

@ -54,7 +54,7 @@ namespace Dalamud.Interface.Internal;
/// <summary> /// <summary>
/// This class manages interaction with the ImGui interface. /// This class manages interaction with the ImGui interface.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class InterfaceManager : IInternalDisposableService internal class InterfaceManager : IInternalDisposableService
{ {
/// <summary> /// <summary>

View file

@ -22,7 +22,7 @@ namespace Dalamud.Interface.Internal;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<ITextureProvider>] [ResolveVia<ITextureProvider>]
[ResolveVia<ITextureSubstitutionProvider>] [ResolveVia<ITextureSubstitutionProvider>]

View file

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Reflection;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
@ -96,7 +97,7 @@ internal class ServicesWidget : IDataWindowWidget
var rowWidth = 0f; var rowWidth = 0f;
foreach (var node in levelNodes) foreach (var node in levelNodes)
rowWidth += ImGui.CalcTextSize(node.TypeName).X + cellPad.X + margin.X; rowWidth += node.DisplayedNameSize.X + cellPad.X + margin.X;
var off = cellPad / 2; var off = cellPad / 2;
if (rowWidth < width) if (rowWidth < width)
@ -107,7 +108,7 @@ internal class ServicesWidget : IDataWindowWidget
foreach (var node in levelNodes) foreach (var node in levelNodes)
{ {
var textSize = ImGui.CalcTextSize(node.TypeName); var textSize = node.DisplayedNameSize;
var cellSize = textSize + cellPad; var cellSize = textSize + cellPad;
var rc = new Vector4(pos + off, pos.X + off.X + cellSize.X, pos.Y + off.Y + cellSize.Y); var rc = new Vector4(pos + off, pos.X + off.X + cellSize.X, pos.Y + off.Y + cellSize.Y);
@ -174,7 +175,7 @@ internal class ServicesWidget : IDataWindowWidget
{ {
foreach (var node in levelNodes) foreach (var node in levelNodes)
{ {
var textSize = ImGui.CalcTextSize(node.TypeName); var textSize = node.DisplayedNameSize;
var cellSize = textSize + cellPad; var cellSize = textSize + cellPad;
var rc = this.nodeRects[node]; var rc = this.nodeRects[node];
@ -188,8 +189,19 @@ internal class ServicesWidget : IDataWindowWidget
dl.AddRectFilled(new(rc.X, rc.Y), new(rc.Z, rc.W), rectHoverRelatedFillColor); dl.AddRectFilled(new(rc.X, rc.Y), new(rc.Z, rc.W), rectHoverRelatedFillColor);
dl.AddRect(new(rc.X, rc.Y), new(rc.Z, rc.W), rectBaseBorderColor); dl.AddRect(new(rc.X, rc.Y), new(rc.Z, rc.W), rectBaseBorderColor);
ImGui.SetCursorScreenPos(new(rc.X, rc.Y));
ImGui.InvisibleButton(node.DisplayedName, new(rc.Z - rc.X, rc.W - rc.Y));
if (ImGui.IsItemHovered() && node.BlockingReason is not null)
ImGui.SetTooltip(node.BlockingReason);
ImGui.SetCursorPos((new Vector2(rc.X, rc.Y) - pos) + ((cellSize - textSize) / 2)); ImGui.SetCursorPos((new Vector2(rc.X, rc.Y) - pos) + ((cellSize - textSize) / 2));
ImGui.TextUnformatted(node.TypeName); ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
ImGui.TextUnformatted(node.DisplayedName);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, node.TypeSuffixColor);
ImGui.TextUnformatted(node.TypeSuffix);
ImGui.PopStyleVar();
ImGui.PopStyleColor();
} }
} }
@ -259,11 +271,44 @@ internal class ServicesWidget : IDataWindowWidget
private readonly List<ServiceDependencyNode> children = new(); private readonly List<ServiceDependencyNode> children = new();
private readonly List<ServiceDependencyNode> invalidParents = new(); private readonly List<ServiceDependencyNode> invalidParents = new();
private ServiceDependencyNode(Type t) => this.Type = t; private ServiceDependencyNode(Type t)
{
this.Type = t;
this.BlockingReason =
t.GetCustomAttribute<ServiceManager.BlockingEarlyLoadedServiceAttribute>()?.BlockReason;
this.Kind = t.GetCustomAttribute<ServiceManager.ServiceAttribute>()?.Kind ??
ServiceManager.ServiceKind.None;
this.DisplayedName = this.Type.Name;
this.TypeSuffix = this.Kind switch {
ServiceManager.ServiceKind.ProvidedService => " (P)",
ServiceManager.ServiceKind.EarlyLoadedService => " (E)",
ServiceManager.ServiceKind.BlockingEarlyLoadedService => " (B)",
ServiceManager.ServiceKind.ScopedService => " (S)",
var x => $" (? {x})",
};
this.TypeSuffixColor = this.Kind switch {
ServiceManager.ServiceKind.ProvidedService => ImGui.GetColorU32(ImGuiColors.DalamudGrey),
ServiceManager.ServiceKind.EarlyLoadedService => ImGui.GetColorU32(ImGuiColors.DalamudWhite),
ServiceManager.ServiceKind.BlockingEarlyLoadedService => ImGui.GetColorU32(ImGuiColors.ParsedPink),
ServiceManager.ServiceKind.ScopedService => ImGui.GetColorU32(ImGuiColors.ParsedGreen),
_ => ImGui.GetColorU32(ImGuiColors.DalamudRed),
};
}
public Type Type { get; } public Type Type { get; }
public string TypeName => this.Type.Name; public string DisplayedName { get; }
public string TypeSuffix { get; }
public uint TypeSuffixColor { get; }
public Vector2 DisplayedNameSize =>
ImGui.CalcTextSize(this.DisplayedName) + ImGui.CalcTextSize(this.TypeSuffix) with { Y = 0 };
public ServiceManager.ServiceKind Kind { get; }
public string? BlockingReason { get; }
public IReadOnlyList<ServiceDependencyNode> Parents => this.parents; public IReadOnlyList<ServiceDependencyNode> Parents => this.parents;

View file

@ -191,11 +191,6 @@ internal class PluginImageCache : IInternalDisposableService
{ {
iconTexture = null; iconTexture = null;
loadedSince = null; loadedSince = null;
// Wait for the horse icon list to be there, if applicable
var fools = Service<Fools24>.Get();
if (Fools24.IsDayApplicable() && fools.IsWaitingForIconList && !fools.Failed)
return false;
if (manifest == null || manifest.InternalName == null) if (manifest == null || manifest.InternalName == null)
{ {
@ -643,14 +638,6 @@ internal class PluginImageCache : IInternalDisposableService
{ {
if (isThirdParty) if (isThirdParty)
return manifest.IconUrl; return manifest.IconUrl;
var fools = Service<Fools24>.Get();
if (Fools24.IsDayApplicable())
{
var iconLink = fools.GetHorseIconLink(manifest.InternalName);
if (iconLink != null)
return iconLink;
}
return MainRepoDip17ImageUrl.Format(manifest.Dip17Channel!, manifest.InternalName, "icon.png"); return MainRepoDip17ImageUrl.Format(manifest.Dip17Channel!, manifest.InternalName, "icon.png");
} }

View file

@ -268,8 +268,6 @@ internal class PluginInstallerWindow : Window, IDisposable
/// <inheritdoc/> /// <inheritdoc/>
public override void OnOpen() public override void OnOpen()
{ {
Service<Fools24>.Get().NotifyInstallerWindowOpened();
var pluginManager = Service<PluginManager>.Get(); var pluginManager = Service<PluginManager>.Get();
_ = pluginManager.ReloadPluginMastersAsync(); _ = pluginManager.ReloadPluginMastersAsync();

View file

@ -25,7 +25,7 @@ public class ProfilerWindow : Window
/// Initializes a new instance of the <see cref="ProfilerWindow"/> class. /// Initializes a new instance of the <see cref="ProfilerWindow"/> class.
/// </summary> /// </summary>
public ProfilerWindow() public ProfilerWindow()
: base("Profiler", forceMainWindow: true) : base("Profiler")
{ {
} }

View file

@ -29,7 +29,7 @@ namespace Dalamud.Interface.ManagedFontAtlas.Internals;
/// <summary> /// <summary>
/// Factory for the implementation of <see cref="IFontAtlas"/>. /// Factory for the implementation of <see cref="IFontAtlas"/>.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed partial class FontAtlasFactory internal sealed partial class FontAtlasFactory
: IInternalDisposableService, GamePrebakedFontHandle.IGameFontTextureProvider : IInternalDisposableService, GamePrebakedFontHandle.IGameFontTextureProvider
{ {

View file

@ -15,7 +15,7 @@ namespace Dalamud.Interface;
/// Class responsible for managing elements in the title screen menu. /// Class responsible for managing elements in the title screen menu.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class TitleScreenMenu : IServiceType, ITitleScreenMenu internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
{ {
/// <summary> /// <summary>

View file

@ -3,6 +3,7 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using Dalamud.Plugin.Internal;
using Dalamud.Utility; using Dalamud.Utility;
namespace Dalamud.Networking.Http; namespace Dalamud.Networking.Http;
@ -11,7 +12,9 @@ namespace Dalamud.Networking.Http;
/// A service to help build and manage HttpClients with some semblance of Happy Eyeballs (RFC 8305 - IPv4 fallback) /// A service to help build and manage HttpClients with some semblance of Happy Eyeballs (RFC 8305 - IPv4 fallback)
/// awareness. /// awareness.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService($"{nameof(PluginManager)} currently uses this.")]
// ^ TODO: This seems unnecessary, remove the hard dependency at a later time.
// Otherwise, if PM eventually marks this class as required, note that in the comment above.
internal class HappyHttpClient : IInternalDisposableService internal class HappyHttpClient : IInternalDisposableService
{ {
/// <summary> /// <summary>

View file

@ -128,6 +128,11 @@ public sealed class DalamudPluginInterface : IDisposable
/// </summary> /// </summary>
public string InternalName => this.plugin.InternalName; public string InternalName => this.plugin.InternalName;
/// <summary>
/// Gets the plugin's manifest.
/// </summary>
public IPluginManifest Manifest => this.plugin.Manifest;
/// <summary> /// <summary>
/// Gets a value indicating whether this is a dev plugin. /// Gets a value indicating whether this is a dev plugin.
/// </summary> /// </summary>

View file

@ -39,22 +39,10 @@ namespace Dalamud.Plugin.Internal;
/// <summary> /// <summary>
/// Class responsible for loading and unloading plugins. /// 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> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService("Accomodation of plugins that blocks the game startup.")]
#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 : IInternalDisposableService internal partial class PluginManager : IInternalDisposableService
{ {
/// <summary> /// <summary>
@ -72,7 +60,7 @@ internal partial class PluginManager : IInternalDisposableService
private readonly List<RemotePluginManifest> availablePluginsList = new(); private readonly List<RemotePluginManifest> availablePluginsList = new();
private readonly List<AvailablePluginUpdate> updatablePluginsList = new(); private readonly List<AvailablePluginUpdate> updatablePluginsList = new();
private readonly DalamudLinkPayload openInstallerWindowPluginChangelogsLink; private readonly Task<DalamudLinkPayload> openInstallerWindowPluginChangelogsLink;
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get(); private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
@ -86,9 +74,6 @@ internal partial class PluginManager : IInternalDisposableService
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly HappyHttpClient happyHttpClient = Service<HappyHttpClient>.Get(); private readonly HappyHttpClient happyHttpClient = Service<HappyHttpClient>.Get();
[ServiceManager.ServiceDependency]
private readonly ChatGui chatGui = Service<ChatGui>.Get();
static PluginManager() static PluginManager()
{ {
DalamudApiLevel = typeof(PluginManager).Assembly.GetName().Version!.Major; DalamudApiLevel = typeof(PluginManager).Assembly.GetName().Version!.Major;
@ -137,10 +122,16 @@ internal partial class PluginManager : IInternalDisposableService
throw new InvalidDataException("Couldn't deserialize banned plugins manifest."); throw new InvalidDataException("Couldn't deserialize banned plugins manifest.");
} }
this.openInstallerWindowPluginChangelogsLink = this.chatGui.AddChatLinkHandler("Dalamud", 1003, (_, _) => this.openInstallerWindowPluginChangelogsLink =
{ Service<ChatGui>.GetAsync().ContinueWith(
Service<DalamudInterface>.GetNullable()?.OpenPluginInstallerTo(PluginInstallerWindow.PluginInstallerOpenKind.Changelogs); chatGuiTask => chatGuiTask.Result.AddChatLinkHandler(
}); "Dalamud",
1003,
(_, _) =>
{
Service<DalamudInterface>.GetNullable()?.OpenPluginInstallerTo(
PluginInstallerWindow.PluginInstallerOpenKind.Changelogs);
}));
this.configuration.PluginTestingOptIns ??= new(); this.configuration.PluginTestingOptIns ??= new();
this.MainRepo = PluginRepository.CreateMainRepo(this.happyHttpClient); this.MainRepo = PluginRepository.CreateMainRepo(this.happyHttpClient);
@ -304,41 +295,54 @@ internal partial class PluginManager : IInternalDisposableService
/// <param name="updateMetadata">The list of updated plugin metadata.</param> /// <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> /// <param name="header">The header text to send to chat prior to any update info.</param>
public void PrintUpdatedPlugins(List<PluginUpdateStatus>? updateMetadata, string header) public void PrintUpdatedPlugins(List<PluginUpdateStatus>? updateMetadata, string header)
{ => Service<ChatGui>.GetAsync().ContinueWith(
if (updateMetadata is { Count: > 0 }) chatGuiTask =>
{
this.chatGui.Print(new XivChatEntry
{ {
Message = new SeString(new List<Payload>() if (!chatGuiTask.IsCompletedSuccessfully)
{ return;
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("]"),
}),
});
foreach (var metadata in updateMetadata) var chatGui = chatGuiTask.Result;
{ if (updateMetadata is { Count: > 0 })
if (metadata.Status == PluginUpdateStatus.StatusKind.Success)
{ {
this.chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version)); chatGui.Print(
} new XivChatEntry
else {
{ Message = new SeString(
this.chatGui.Print(new XivChatEntry 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)), if (metadata.Status == PluginUpdateStatus.StatusKind.Success)
Type = XivChatType.Urgent, {
}); 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> /// <summary>
/// For a given manifest, determine if the user opted into testing this plugin. /// For a given manifest, determine if the user opted into testing this plugin.
@ -1257,6 +1261,16 @@ internal partial class PluginManager : IInternalDisposableService
/// <returns>The dependency services.</returns> /// <returns>The dependency services.</returns>
private static IEnumerable<Type> ResolvePossiblePluginDependencyServices() 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()) foreach (var serviceType in ServiceManager.GetConcreteServiceTypes())
{ {
if (serviceType == typeof(PluginManager)) if (serviceType == typeof(PluginManager))

View file

@ -15,7 +15,7 @@ namespace Dalamud.Plugin.Internal.Profiles;
/// <summary> /// <summary>
/// Class responsible for managing plugin profiles. /// Class responsible for managing plugin profiles.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService($"Data provider for {nameof(PluginManager)}.")]
internal class ProfileManager : IServiceType internal class ProfileManager : IServiceType
{ {
private static readonly ModuleLog Log = new("PROFMAN"); private static readonly ModuleLog Log = new("PROFMAN");

View file

@ -11,7 +11,7 @@ namespace Dalamud.Plugin.Ipc.Internal;
/// <summary> /// <summary>
/// This class facilitates sharing data-references of standard types between plugins without using more expensive IPC. /// This class facilitates sharing data-references of standard types between plugins without using more expensive IPC.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class DataShare : IServiceType internal class DataShare : IServiceType
{ {
/// <summary> /// <summary>

View file

@ -638,10 +638,15 @@ internal static class ServiceManager
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BlockingEarlyLoadedServiceAttribute"/> class. /// Initializes a new instance of the <see cref="BlockingEarlyLoadedServiceAttribute"/> class.
/// </summary> /// </summary>
public BlockingEarlyLoadedServiceAttribute() /// <param name="blockReason">Reason of blocking the game startup.</param>
public BlockingEarlyLoadedServiceAttribute(string blockReason)
: base(ServiceKind.BlockingEarlyLoadedService) : base(ServiceKind.BlockingEarlyLoadedService)
{ {
this.BlockReason = blockReason;
} }
/// <summary>Gets the reason of blocking the startup of the game.</summary>
public string BlockReason { get; }
} }
/// <summary> /// <summary>

View file

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
using Dalamud.Utility;
using Dalamud.Utility.Timing; using Dalamud.Utility.Timing;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -198,13 +199,16 @@ internal static class Service<T> where T : IServiceType
.ToArray(); .ToArray();
if (offenders.Any()) if (offenders.Any())
{ {
ServiceManager.Log.Error( const string bels = nameof(ServiceManager.BlockingEarlyLoadedServiceAttribute);
"{me} is a {bels}; it can only depend on {bels} and {ps}.\nOffending dependencies:\n{offenders}", const string ps = nameof(ServiceManager.ProvidedServiceAttribute);
typeof(T), var errmsg =
nameof(ServiceManager.BlockingEarlyLoadedServiceAttribute), $"{typeof(T)} is a {bels}; it can only depend on {bels} and {ps}.\nOffending dependencies:\n" +
nameof(ServiceManager.BlockingEarlyLoadedServiceAttribute), string.Join("\n", offenders.Select(x => $"\t* {x.Name}"));
nameof(ServiceManager.ProvidedServiceAttribute), #if DEBUG
string.Join("\n", offenders.Select(x => $"\t* {x.Name}"))); Util.Fatal(errmsg, "Service Dependency Check");
#else
ServiceManager.Log.Error(errmsg);
#endif
} }
} }

View file

@ -23,7 +23,8 @@ namespace Dalamud.Storage.Assets;
/// A concrete class for <see cref="IDalamudAssetManager"/>. /// A concrete class for <see cref="IDalamudAssetManager"/>.
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService(
"Ensuring that it is worth continuing loading Dalamud, by checking if all required assets are properly available.")]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IDalamudAssetManager>] [ResolveVia<IDalamudAssetManager>]
#pragma warning restore SA1015 #pragma warning restore SA1015

@ -1 +1 @@
Subproject commit 76f15950d210d5196d942cc8439c2e48bfffd0ee Subproject commit 98c1de8b94bcdfce4dc79a61cc0e8b17773777f0