Skip to content

Commit

Permalink
[TargetCastBarCountdown] Modernize Tweak (#645)
Browse files Browse the repository at this point in the history
* [`TargetCastBarCountdown`] Modernize Tweak

* [`TargetCastBarCountdown`] Fix not saving on change
  • Loading branch information
MidoriKami authored Oct 18, 2023
1 parent 12e865d commit 1e7bd8a
Showing 1 changed file with 74 additions and 131 deletions.
205 changes: 74 additions & 131 deletions Tweaks/UiAdjustment/TargetCastbarCountdown.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,42 @@
#nullable enable
using System;
using System.Numerics;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface.Utility;
using FFXIVClientStructs.FFXIV.Client.Graphics;
using FFXIVClientStructs.FFXIV.Component.GUI;
using ImGuiNET;
using SimpleTweaksPlugin.Events;
using SimpleTweaksPlugin.TweakSystem;
using SimpleTweaksPlugin.Utility;

namespace SimpleTweaksPlugin.Tweaks.UiAdjustment;

public unsafe class TargetCastbarCountdown : UiAdjustments.SubTweak
{
public override string Name => "Target Castbar Countdown";
public override string Description => "Displays time remaining on targets ability cast.";
protected override string Author => "MidoriKami";
[TweakName("Target Castbar Countdown")]
[TweakDescription("Displays time remaining on targets ability cast.")]
[TweakAuthor("MidoriKami")]
[TweakAutoConfig]
[TweakReleaseVersion("1.8.3.0")]
[Changelog("1.8.3.1", "Add TopRight option for displaying countdown")]
[Changelog("1.8.9.0", "Add option to disable on primary target")]
public unsafe class TargetCastbarCountdown : UiAdjustments.SubTweak {
private uint CastBarTextNodeId => CustomNodes.Get(this, "Countdown");

private static AtkUnitBase* AddonTargetInfoCastBar => Common.GetUnitBase("_TargetInfoCastBar");
private static AtkUnitBase* AddonTargetInfo => Common.GetUnitBase("_TargetInfo");
private static AtkUnitBase* AddonFocusTargetInfo => Common.GetUnitBase("_FocusTargetInfo");

private const uint CastbarTextNodeId = 3000U;

private readonly ByteColor textColor = new() { R = 255, G = 255, B = 255, A = 255 };
private readonly ByteColor edgeColor = new() { R = 142, G = 106, B = 12, A = 255 };
private readonly ByteColor backgroundColor = new() { R = 0, G = 0, B = 0, A = 0 };

private Config TweakConfig { get; set; } = null!;

private class Config : TweakConfig
{
private class Config : TweakConfig {
public bool PrimaryTargetEnabled = true;
public bool FocusTargetEnabled = false;
public NodePosition FocusTargetPosition = NodePosition.Left;
public NodePosition CastbarPosition = NodePosition.BottomLeft;
}

private enum NodePosition
{
private enum NodePosition {
Right,
Left,
TopLeft,
Expand All @@ -47,47 +45,38 @@ private enum NodePosition
BottomRight
}

protected override void ConfigChanged()
{
SaveConfig(TweakConfig);
FreeAllNodes();
}

protected override DrawConfigDelegate DrawConfigTree => (ref bool hasChanged) =>
{
if (ImGui.Checkbox("Enable Primary Target", ref TweakConfig.PrimaryTargetEnabled)) hasChanged = true;
private void DrawConfig() {
var hasChanged = ImGui.Checkbox("Enable Primary Target", ref TweakConfig.PrimaryTargetEnabled);

if (ImGui.Checkbox("Enable Focus Target", ref TweakConfig.FocusTargetEnabled)) hasChanged = true;
hasChanged |= ImGui.Checkbox("Enable Focus Target", ref TweakConfig.FocusTargetEnabled);

ImGui.TextUnformatted("Select which direction relative to Cast Bar to show countdown");
if (TweakConfig is { PrimaryTargetEnabled: false, FocusTargetEnabled: false })
{
if (TweakConfig is { PrimaryTargetEnabled: false, FocusTargetEnabled: false }) {
ImGuiHelpers.ScaledIndent(20.0f);
ImGui.TextUnformatted("No Castbars Selected");
ImGui.TextUnformatted("No CastBars Selected");
ImGuiHelpers.ScaledIndent(-20.0f);
}

if (TweakConfig.FocusTargetEnabled)
{
if (DrawCombo(ref TweakConfig.FocusTargetPosition, "Focus Target")) hasChanged = true;
if (TweakConfig.FocusTargetEnabled) {
hasChanged |= DrawCombo(ref TweakConfig.FocusTargetPosition, "Focus Target");
}

if (TweakConfig.PrimaryTargetEnabled)
{
if (DrawCombo(ref TweakConfig.CastbarPosition, "Primary Target")) hasChanged = true;
if (TweakConfig.PrimaryTargetEnabled) {
hasChanged |= DrawCombo(ref TweakConfig.CastbarPosition, "Primary Target");
}
};

private bool DrawCombo(ref NodePosition setting, string label)
{
if (hasChanged) {
SaveConfig(TweakConfig);
FreeAllNodes();
}
}

private bool DrawCombo(ref NodePosition setting, string label) {
var regionSize = ImGui.GetContentRegionAvail();
ImGui.SetNextItemWidth(regionSize.X * 1.0f / 3.0f);
if (ImGui.BeginCombo(label, setting.ToString()))
{
foreach (var direction in Enum.GetValues<NodePosition>())
{
if (ImGui.Selectable(direction.ToString(), setting == direction))
{
if (ImGui.BeginCombo(label, setting.ToString())) {
foreach (var direction in Enum.GetValues<NodePosition>()) {
if (ImGui.Selectable(direction.ToString(), setting == direction)) {
setting = direction;
return true;
}
Expand All @@ -98,108 +87,60 @@ private bool DrawCombo(ref NodePosition setting, string label)

return false;
}

public override void Setup()
{
AddChangelogNewTweak("1.8.3.0");
AddChangelog("1.8.3.1", "Add TopRight option for displaying countdown");
AddChangelog("1.8.9.0", "Add option to disable on primary target");
base.Setup();
}

protected override void Enable()
{
TweakConfig = LoadConfig<Config>() ?? new Config();

Common.FrameworkUpdate += OnFrameworkUpdate;
Service.ClientState.EnterPvP += OnEnterPvP;
Service.ClientState.LeavePvP += OnLeavePvP;
base.Enable();
}

protected override void Disable()
{
SaveConfig(TweakConfig);

Common.FrameworkUpdate -= OnFrameworkUpdate;
Service.ClientState.EnterPvP -= OnEnterPvP;
Service.ClientState.LeavePvP -= OnLeavePvP;

FreeAllNodes();

base.Disable();
}

private void OnEnterPvP()
{
Common.FrameworkUpdate -= OnFrameworkUpdate;

protected override void Disable() {
FreeAllNodes();
}

private void OnLeavePvP()
{
Common.FrameworkUpdate += OnFrameworkUpdate;
}

private void OnFrameworkUpdate()
{
// If we get here while in any kind of PvP area, unregister this callback and free nodes.
if (Service.ClientState.IsPvP)
{
Common.FrameworkUpdate -= OnFrameworkUpdate;
FreeAllNodes();
return;
}
[AddonPostRequestedUpdate("_TargetInfoCastBar", "_TargetInfo", "_FocusTargetInfo")]
private void OnAddonRequestedUpdate(AddonArgs args) {
if (Service.ClientState.IsPvP) return;

// Castbar is split from target info
if (AddonTargetInfoCastBar is not null && AddonTargetInfoCastBar->IsVisible && TweakConfig.PrimaryTargetEnabled) UpdateAddon(AddonTargetInfoCastBar, 7, 2, Service.Targets.Target);
var addon = (AtkUnitBase*) args.Addon;

// Castbar is combined with target info
if (AddonTargetInfo is not null && AddonTargetInfo->IsVisible && TweakConfig.PrimaryTargetEnabled) UpdateAddon(AddonTargetInfo, 15, 10, Service.Targets.Target);
switch (args.AddonName) {
case "_TargetInfoCastBar" when addon->IsVisible:
UpdateAddon(addon, 7, 2, Service.Targets.Target);
break;

// Focus target castbar
if (AddonFocusTargetInfo is not null && AddonFocusTargetInfo->IsVisible && TweakConfig.FocusTargetEnabled) UpdateAddon(AddonFocusTargetInfo, 8, 3, Service.Targets.FocusTarget, true);
case "_TargetInfo" when addon->IsVisible:
UpdateAddon(addon, 15, 10, Service.Targets.Target);
break;

case "_FocusTargetInfo" when addon->IsVisible:
UpdateAddon(addon, 8, 3, Service.Targets.FocusTarget, true);
break;
}
}

private void UpdateAddon(AtkUnitBase* addon, uint visibilityNodeId, uint positioningNodeId, GameObject? target, bool focusTarget = false)
{
private void UpdateAddon(AtkUnitBase* addon, uint visibilityNodeId, uint positioningNodeId, GameObject? target, bool focusTarget = false) {
var interruptNode = Common.GetNodeByID<AtkImageNode>(&addon->UldManager, visibilityNodeId);
var castbarNode = Common.GetNodeByID(&addon->UldManager, positioningNodeId);
if (interruptNode is not null && castbarNode is not null)
{
TryMakeNodes(addon, castbarNode, focusTarget);
var castBarNode = Common.GetNodeByID(&addon->UldManager, positioningNodeId);
if (interruptNode is not null && castBarNode is not null) {
TryMakeNodes(addon, castBarNode, focusTarget);
UpdateIcons(interruptNode->AtkResNode.IsVisible, addon, target);
}
}

private void TryMakeNodes(AtkUnitBase* parent, AtkResNode* positionNode, bool focusTarget)
{
if (!UiHelper.IsAddonReady(parent)) return;

var textNode = Common.GetNodeByID<AtkTextNode>(&parent->UldManager, CastbarTextNodeId);
if (textNode is null) MakeTextNode(parent, CastbarTextNodeId, positionNode, focusTarget);
private void TryMakeNodes(AtkUnitBase* parent, AtkResNode* positionNode, bool focusTarget) {
var textNode = Common.GetNodeByID<AtkTextNode>(&parent->UldManager, CastBarTextNodeId);
if (textNode is null) MakeTextNode(parent, CastBarTextNodeId, positionNode, focusTarget);
}

private void UpdateIcons(bool castBarVisible, AtkUnitBase* parent, GameObject? target)
{
var textNode = Common.GetNodeByID<AtkTextNode>(&parent->UldManager, CastbarTextNodeId);

private void UpdateIcons(bool castBarVisible, AtkUnitBase* parent, GameObject? target) {
var textNode = Common.GetNodeByID<AtkTextNode>(&parent->UldManager, CastBarTextNodeId);
if (textNode is null) return;

if (target as BattleChara is { IsCasting: true } targetInfo && castBarVisible && targetInfo.TotalCastTime > targetInfo.CurrentCastTime)
{
if (target as BattleChara is { IsCasting: true } targetInfo && castBarVisible && targetInfo.TotalCastTime > targetInfo.CurrentCastTime) {
textNode->AtkResNode.ToggleVisibility(true);
textNode->SetText($"{targetInfo.TotalCastTime - targetInfo.CurrentCastTime:00.00}");
}
else
{
else {
textNode->AtkResNode.ToggleVisibility(false);
}
}

private void MakeTextNode(AtkUnitBase* parent, uint nodeId, AtkResNode* positioningNode, bool focusTarget)
{
private void MakeTextNode(AtkUnitBase* parent, uint nodeId, AtkResNode* positioningNode, bool focusTarget) {
var textNode = UiHelper.MakeTextNode(nodeId);

textNode->AtkResNode.NodeFlags = NodeFlags.Visible | NodeFlags.Enabled | NodeFlags.AnchorTop | NodeFlags.AnchorLeft;
Expand All @@ -213,13 +154,12 @@ private void MakeTextNode(AtkUnitBase* parent, uint nodeId, AtkResNode* position
textNode->LineSpacing = 20;
textNode->AlignmentFontType = 37;
textNode->FontSize = 20;
textNode->TextFlags = (byte) (TextFlags.Edge);
textNode->TextFlags = (byte) TextFlags.Edge;

textNode->AtkResNode.SetWidth(80);
textNode->AtkResNode.SetHeight(22);

var nodePosition = (focusTarget ? TweakConfig.FocusTargetPosition : TweakConfig.CastbarPosition) switch
{
var nodePosition = (focusTarget ? TweakConfig.FocusTargetPosition : TweakConfig.CastbarPosition) switch {
NodePosition.Left => new Vector2(positioningNode->X - 80, positioningNode->Y),
NodePosition.Right => new Vector2(positioningNode->X + positioningNode->Width, positioningNode->Y),
NodePosition.TopLeft => new Vector2(positioningNode->X, positioningNode->Y - 14),
Expand All @@ -234,18 +174,21 @@ private void MakeTextNode(AtkUnitBase* parent, uint nodeId, AtkResNode* position
UiHelper.LinkNodeAtEnd((AtkResNode*) textNode, parent);
}

private void FreeAllNodes()
{
TryFreeTextNode(AddonTargetInfoCastBar, CastbarTextNodeId);
TryFreeTextNode(AddonTargetInfo, CastbarTextNodeId);
TryFreeTextNode(AddonFocusTargetInfo, CastbarTextNodeId);
private void FreeAllNodes() {
var addonTargetInfoCastBar = Common.GetUnitBase("_TargetInfoCastBar");
var addonTargetInfo = Common.GetUnitBase("_TargetInfo");
var addonFocusTargetInfo = Common.GetUnitBase("_FocusTargetInfo");

TryFreeTextNode(addonTargetInfoCastBar, CastBarTextNodeId);
TryFreeTextNode(addonTargetInfo, CastBarTextNodeId);
TryFreeTextNode(addonFocusTargetInfo, CastBarTextNodeId);
}

private void TryFreeTextNode(AtkUnitBase* addon, uint nodeId) {
if (addon == null) return;

var textNode = Common.GetNodeByID<AtkTextNode>(&addon->UldManager, nodeId);
if (textNode is not null)
{
if (textNode is not null) {
UiHelper.UnlinkAndFreeTextNode(textNode, addon);
}
}
Expand Down

0 comments on commit 1e7bd8a

Please sign in to comment.