Files
AetherBags/KamiToolKit/Overlay/OverlayController.cs
T
KnackAtNite 8db4ce6094
Debug Build and Test / Build against Latest Dalamud (push) Has been cancelled
Debug Build and Test / Build against Staging Dalamud (push) Has been cancelled
Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 14:46:31 -05:00

229 lines
7.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using KamiToolKit.Classes;
using KamiToolKit.Enums;
namespace KamiToolKit.Overlay;
public unsafe class OverlayController : IDisposable {
private readonly Dictionary<OverlayLayer, List<OverlayNode>> overlayNodes = [];
private readonly Dictionary<OverlayLayer, OverlayAddonState> addonState = [];
private ControllerState controllerState = ControllerState.WaitForNameplate;
public OverlayController() {
ClearState();
DalamudInterface.Instance.AddonLifecycle.RegisterListener(AddonEvent.PreFinalize, "NamePlate", OnNamePlatePreFinalize);
foreach (var overlayLayer in Enum.GetValues<OverlayLayer>()) {
var addonName = overlayLayer.Description;
DalamudInterface.Instance.AddonLifecycle.RegisterListener(AddonEvent.PreUpdate, addonName, OnOverlayAddonUpdate);
DalamudInterface.Instance.AddonLifecycle.RegisterListener(AddonEvent.PreFinalize, addonName, OnOverlayAddonFinalize);
}
BeginStateCheck();
}
public void Dispose() {
DalamudInterface.Instance.AddonLifecycle.UnregisterListener(AddonEvent.PreFinalize, "NamePlate");
DalamudInterface.Instance.AddonLifecycle.UnregisterListener(OnOverlayAddonFinalize, OnOverlayAddonUpdate);
foreach (var node in overlayNodes.SelectMany(nodeList => nodeList.Value)) {
node.Dispose();
}
overlayNodes.Clear();
}
//
// State management (framework thread)
//
private void ClearState() {
controllerState = ControllerState.WaitForNameplate;
foreach (var overlayLayer in Enum.GetValues<OverlayLayer>()) {
addonState[overlayLayer] = OverlayAddonState.None;
}
}
private void BeginStateCheck() {
DalamudInterface.Instance.Framework.Update -= CheckOverlayState;
DalamudInterface.Instance.Framework.Update += CheckOverlayState;
}
private void CheckOverlayState(IFramework framework) {
switch (controllerState) {
case ControllerState.WaitForNameplate:
CheckNameplateReady();
break;
case ControllerState.WaitForReady:
CheckOverlayAddonsReady();
break;
case ControllerState.Ready:
DalamudInterface.Instance.Framework.Update -= CheckOverlayState;
break;
}
}
private void CheckNameplateReady() {
var nameplate = RaptureAtkUnitManager.Instance()->GetAddonByName("NamePlate");
if (nameplate is null) return;
if (!nameplate->IsReady) return;
foreach (var overlayLayer in Enum.GetValues<OverlayLayer>()) {
var addon = RaptureAtkUnitManager.Instance()->GetAddonByName(overlayLayer.Description);
if (addon is null) {
if (addonState[overlayLayer] == OverlayAddonState.None) {
addonState[overlayLayer] = OverlayAddonState.WaitForReady;
CreateOverlayAddon(overlayLayer).Open();
}
}
else {
addonState[overlayLayer] = OverlayAddonState.WaitForReady;
}
}
controllerState = ControllerState.WaitForReady;
}
private void CheckOverlayAddonsReady() {
var totalAddons = Enum.GetValues<OverlayLayer>().Length;
var totalAddonsReady = 0;
foreach (var overlayLayer in Enum.GetValues<OverlayLayer>()) {
var addon = RaptureAtkUnitManager.Instance()->GetAddonByName(overlayLayer.Description);
if (addon is null) continue;
if (!addon->IsReady) continue;
if (addonState[overlayLayer] is OverlayAddonState.WaitForReady) {
AttachAllNodes(overlayLayer);
addonState[overlayLayer] = OverlayAddonState.Ready;
}
totalAddonsReady++;
}
if (totalAddonsReady == totalAddons) {
controllerState = ControllerState.Ready;
}
}
private void AttachAllNodes(OverlayLayer layer) {
if (!overlayNodes.TryGetValue(layer, out var list)) return;
var addon = RaptureAtkUnitManager.Instance()->GetAddonByName(layer.Description);
if (addon is null) return;
foreach (var node in list) {
AttachNode(addon, node);
}
}
//
// Public node access
//
public void CreateNode(Func<OverlayNode> creationFunction) => DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
AddNode(creationFunction());
});
public void AddNode(OverlayNode node) => DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
overlayNodes.TryAdd(node.OverlayLayer, []);
if (overlayNodes[node.OverlayLayer].Contains(node)) return;
overlayNodes[node.OverlayLayer].Add(node);
if (addonState[node.OverlayLayer] is not OverlayAddonState.Ready) return;
var addon = RaptureAtkUnitManager.Instance()->GetAddonByName(node.OverlayLayer.Description);
if (addon is null) return;
AttachNode(addon, node);
});
public void RemoveNode(OverlayNode node) => DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
if (!overlayNodes.TryGetValue(node.OverlayLayer, out var list)) return;
if (list.Remove(node)) {
node.Dispose();
}
});
public void RemoveAllNodes() => DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
foreach (var node in overlayNodes.SelectMany(set => set.Value).ToList()) {
RemoveNode(node);
}
});
//
// Events
//
private void OnNamePlatePreFinalize(AddonEvent type, AddonArgs args) {
ClearState();
foreach (var overlayLayer in Enum.GetValues<OverlayLayer>()) {
if (!overlayNodes.TryGetValue(overlayLayer, out var list)) continue;
foreach (var node in list) {
node.DetachNode();
}
}
BeginStateCheck();
}
private void OnOverlayAddonFinalize(AddonEvent type, AddonArgs args) {
var addon = (AtkUnitBase*)args.Addon.Address;
var overlayLayer = addon->DepthLayer.GetOverlayLayer();
if (overlayNodes.TryGetValue(overlayLayer, out var list)) {
foreach (var node in list) {
node.DetachNode();
}
}
addonState[overlayLayer] = OverlayAddonState.None;
}
private void OnOverlayAddonUpdate(AddonEvent type, AddonArgs args) {
var addon = (AtkUnitBase*)args.Addon.Address;
var overlayLayer = addon->DepthLayer.GetOverlayLayer();
if (addonState[overlayLayer] is not OverlayAddonState.Ready) return;
if (!overlayNodes.TryGetValue(overlayLayer, out var list)) return;
foreach (var node in list) {
node.Update();
}
}
//
// Helpers
//
private static OverlayAddon CreateOverlayAddon(OverlayLayer layer) => new() {
Title = layer.Description,
InternalName = layer.Description,
DepthLayer = layer.DepthLayer,
IsOverlayAddon = true,
};
private static void AttachNode(AtkUnitBase* addon, OverlayNode node) {
node.NodeId = (uint)addon->UldManager.NodeListCount + 1;
node.AttachNode(addon);
}
}