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>
[PluginInterface]
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015
[ResolveVia<IDataManager>]
#pragma warning restore SA1015

View file

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using CheapLoc;
using Dalamud.Configuration.Internal;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Gui;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
@ -25,7 +26,7 @@ namespace Dalamud.Game;
/// <summary>
/// Chat events and public helper functions.
/// </summary>
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal class ChatHandlers : IServiceType
{
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
@ -290,13 +291,20 @@ internal class ChatHandlers : IServiceType
var chatGui = Service<ChatGui>.GetNullable();
var pluginManager = Service<PluginManager>.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.");
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())
{
// Plugins aren't ready yet.

View file

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

View file

@ -16,7 +16,7 @@ namespace Dalamud.Game.ClientState.Buddy;
/// </summary>
[PluginInterface]
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015
[ResolveVia<IBuddyList>]
#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.
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal sealed class ClientState : IInternalDisposableService, IClientState
{
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.
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
internal sealed partial class Condition : IInternalDisposableService, ICondition
[ServiceManager.EarlyLoadedService]
internal sealed class Condition : IInternalDisposableService, ICondition
{
/// <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.

View file

@ -428,7 +428,14 @@ public enum ConditionFlag
/// <summary>
/// Unable to execute command while bound by duty.
/// </summary>
[Obsolete("Use InDutyQueue")]
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>
/// Unable to execute command while readying to visit another World.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,6 +12,7 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Memory;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.System.String;
@ -28,7 +29,7 @@ namespace Dalamud.Game.Gui;
/// This class handles interacting with the native chat UI.
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
{
private static readonly ModuleLog Log = new("ChatGui");
@ -61,7 +62,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
this.populateItemLinkHook.Enable();
this.interactableLinkClickedHook.Enable();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
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);
}
/// <inheritdoc/>
public void Print(string message, string? messageTag = null, ushort? tagColor = null)
{
this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor);
}
/// <inheritdoc/>
public void Print(SeString message, string? messageTag = null, ushort? tagColor = null)
{
this.PrintTagged(message, this.configuration.GeneralChatType, messageTag, tagColor);
}
/// <inheritdoc/>
public void PrintError(string message, string? messageTag = null, ushort? tagColor = null)
{
this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor);
}
/// <inheritdoc/>
public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null)
{
this.PrintTagged(message, XivChatType.Urgent, messageTag, tagColor);
}
/// <summary>
/// Process a chat queue.
/// </summary>
@ -154,9 +155,29 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
while (this.chatQueue.Count > 0)
{
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 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));
@ -193,7 +214,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
lock (this.dalamudLinkHandlers)
{
var changed = false;
foreach (var handler in this.RegisteredLinkHandlers.Keys.Where(k => k.PluginName == pluginName))
changed |= this.dalamudLinkHandlers.Remove(handler);
if (changed)
@ -230,18 +251,18 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
builder.AddText($"[{tag}] ");
}
}
this.Print(new XivChatEntry
{
Message = builder.AddText(message).Build(),
Type = channel,
});
}
private void PrintTagged(SeString message, XivChatType channel, string? tag, ushort? color)
{
var builder = new SeStringBuilder();
if (!tag.IsNullOrEmpty())
{
if (color is not null)
@ -253,7 +274,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
builder.AddText($"[{tag}] ");
}
}
this.Print(new XivChatEntry
{
Message = builder.Build().Append(message),
@ -424,16 +445,16 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
this.chatGuiService.ChatMessageHandled += this.OnMessageHandledForward;
this.chatGuiService.ChatMessageUnhandled += this.OnMessageUnhandledForward;
}
/// <inheritdoc/>
public event IChatGui.OnMessageDelegate? ChatMessage;
/// <inheritdoc/>
public event IChatGui.OnCheckMessageHandledDelegate? CheckMessageHandled;
/// <inheritdoc/>
public event IChatGui.OnMessageHandledDelegate? ChatMessageHandled;
/// <inheritdoc/>
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
@ -459,23 +480,23 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
this.ChatMessageHandled = null;
this.ChatMessageUnhandled = null;
}
/// <inheritdoc/>
public void Print(XivChatEntry chat)
=> this.chatGuiService.Print(chat);
/// <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);
/// <inheritdoc/>
public void Print(SeString message, string? messageTag = null, ushort? tagColor = null)
=> this.chatGuiService.Print(message, messageTag, tagColor);
/// <inheritdoc/>
public void PrintError(string message, string? messageTag = null, ushort? tagColor = null)
=> this.chatGuiService.PrintError(message, messageTag, tagColor);
/// <inheritdoc/>
public void PrintError(SeString message, string? messageTag = null, ushort? tagColor = null)
=> 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.
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
{
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".
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
{
/// <summary>

View file

@ -26,7 +26,7 @@ namespace Dalamud.Game.Gui;
/// A class handling many aspects of the in-game UI.
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
{
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.
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderGui
{
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.
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
{
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.
/// </summary>
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal class GameInventory : IInternalDisposableService
{
private readonly List<GameInventoryPluginScoped> subscribersPendingChange = new();

View file

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

View file

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

View file

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

View file

@ -14,7 +14,7 @@ namespace Dalamud.Hooking.WndProcHook;
/// <summary>
/// Manages WndProc hooks for game main window and extra ImGui viewport windows.
/// </summary>
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal sealed class WndProcHookManager : IInternalDisposableService
{
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.
/// </summary>
[PluginInterface]
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015
[ResolveVia<IDragDropManager>]
#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>
/// This class manages interaction with the ImGui interface.
/// </summary>
[ServiceManager.BlockingEarlyLoadedService]
[ServiceManager.EarlyLoadedService]
internal class InterfaceManager : IInternalDisposableService
{
/// <summary>

View file

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

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Reflection;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
@ -96,7 +97,7 @@ internal class ServicesWidget : IDataWindowWidget
var rowWidth = 0f;
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;
if (rowWidth < width)
@ -107,7 +108,7 @@ internal class ServicesWidget : IDataWindowWidget
foreach (var node in levelNodes)
{
var textSize = ImGui.CalcTextSize(node.TypeName);
var textSize = node.DisplayedNameSize;
var cellSize = textSize + cellPad;
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)
{
var textSize = ImGui.CalcTextSize(node.TypeName);
var textSize = node.DisplayedNameSize;
var cellSize = textSize + cellPad;
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.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.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> 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 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;

View file

@ -191,11 +191,6 @@ internal class PluginImageCache : IInternalDisposableService
{
iconTexture = 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)
{
@ -643,14 +638,6 @@ internal class PluginImageCache : IInternalDisposableService
{
if (isThirdParty)
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");
}

View file

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

View file

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

View file

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

View file

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

View file

@ -3,6 +3,7 @@ using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Dalamud.Plugin.Internal;
using Dalamud.Utility;
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)
/// awareness.
/// </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
{
/// <summary>

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>

View file

@ -39,22 +39,10 @@ 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
[ServiceManager.BlockingEarlyLoadedService("Accomodation of plugins that blocks the game startup.")]
internal partial class PluginManager : IInternalDisposableService
{
/// <summary>
@ -72,7 +60,7 @@ internal partial class PluginManager : IInternalDisposableService
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 : IInternalDisposableService
[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,10 +122,16 @@ internal partial class PluginManager : IInternalDisposableService
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);
@ -304,41 +295,54 @@ internal partial class PluginManager : IInternalDisposableService
/// <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.
@ -1257,6 +1261,16 @@ internal partial class PluginManager : IInternalDisposableService
/// <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))

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

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

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

View file

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Utility;
using Dalamud.Utility.Timing;
using JetBrains.Annotations;
@ -198,13 +199,16 @@ internal static class Service<T> where T : IServiceType
.ToArray();
if (offenders.Any())
{
ServiceManager.Log.Error(
"{me} is a {bels}; it can only depend on {bels} and {ps}.\nOffending dependencies:\n{offenders}",
typeof(T),
nameof(ServiceManager.BlockingEarlyLoadedServiceAttribute),
nameof(ServiceManager.BlockingEarlyLoadedServiceAttribute),
nameof(ServiceManager.ProvidedServiceAttribute),
string.Join("\n", offenders.Select(x => $"\t* {x.Name}")));
const string bels = nameof(ServiceManager.BlockingEarlyLoadedServiceAttribute);
const string ps = nameof(ServiceManager.ProvidedServiceAttribute);
var errmsg =
$"{typeof(T)} is a {bels}; it can only depend on {bels} and {ps}.\nOffending dependencies:\n" +
string.Join("\n", offenders.Select(x => $"\t* {x.Name}"));
#if DEBUG
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"/>.
/// </summary>
[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
[ResolveVia<IDalamudAssetManager>]
#pragma warning restore SA1015

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