diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..bf5b87b
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+ko_fi: ArchiTed
\ No newline at end of file
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
new file mode 100644
index 0000000..66dcb75
--- /dev/null
+++ b/.github/workflows/release-please.yml
@@ -0,0 +1,87 @@
+on:
+ push:
+ branches:
+ - release
+name: release-please
+jobs:
+ release-please:
+ name : releasePlz
+ runs-on: ubuntu-latest
+ outputs:
+ released: ${{ steps.rp.outputs.releases_created }}
+ upload_url: ${{ steps.rp.outputs.upload_url }}
+ steps:
+ - id: rp
+ uses: google-github-actions/release-please-action@v3
+ with:
+ release-type: node
+ package-name: release-please-action
+ default-branch: release
+
+ build:
+ name : build
+ needs: release-please
+ if: ${{ needs.release-please.outputs.released }}
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ submodules: recursive
+
+ - name: Set up .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 7.0.x
+
+ - name: Restore Dependencies
+ run: dotnet restore
+
+ - name: Download Dalamud
+ run: |
+ Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip
+ Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev"
+
+ - name: Build Plugin
+ run: |
+ invoke-expression 'dotnet build --no-restore --configuration Release RotationSolver'
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v3
+ with:
+ path: .\RotationSolver\bin\Release\net7.0-windows\RotationSolver\
+
+ - name: publish on version change
+ id: publish_nuget
+ uses: alirezanet/publish-nuget@v3.0.4
+ with:
+ PROJECT_FILE_PATH: RotationSolver.Basic/RotationSolver.Basic.csproj
+ VERSION_FILE_PATH: Directory.Build.props
+ NUGET_KEY: ${{secrets.nuget_api_key}}
+
+ release:
+ name: release
+ needs: [release-please, build]
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Download Build Artifact
+ uses: actions/download-artifact@v3
+
+ - name: Upload Release Asset
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ needs.release-please.outputs.upload_url }}
+ asset_path: artifact/latest.zip
+ asset_name: latest.zip
+ asset_content_type: application/zip
+
+ - name: Trigger Repo Update
+ uses: peter-evans/repository-dispatch@v2
+ with:
+ token: ${{ secrets.PAT }}
+ repository: ${{ github.repository_owner }}/Dalamud_Plugins
+ event-type: new-release
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..fee2268
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "ECommons"]
+ path = ECommons
+ url = https://github.com/NightmareXIV/ECommons
diff --git a/ActionTimeline.sln b/ActionTimeline.sln
deleted file mode 100644
index 5c13a26..0000000
--- a/ActionTimeline.sln
+++ /dev/null
@@ -1,31 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.31729.503
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActionTimeline", "ActionTimeline\ActionTimeline.csproj", "{47ED9FA0-F4D6-48B3-A0C7-6252FC014160}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{226FB92B-E6FD-4D1A-9D6F-C4292450B706}"
- ProjectSection(SolutionItems) = preProject
- .editorconfig = .editorconfig
- .gitignore = .gitignore
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {47ED9FA0-F4D6-48B3-A0C7-6252FC014160}.Debug|Any CPU.ActiveCfg = Debug|x64
- {47ED9FA0-F4D6-48B3-A0C7-6252FC014160}.Debug|Any CPU.Build.0 = Debug|x64
- {47ED9FA0-F4D6-48B3-A0C7-6252FC014160}.Release|Any CPU.ActiveCfg = Release|x64
- {47ED9FA0-F4D6-48B3-A0C7-6252FC014160}.Release|Any CPU.Build.0 = Release|x64
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {8890BB4A-4C58-48B5-9DE4-C275BFE8FDCC}
- EndGlobalSection
-EndGlobal
diff --git a/ActionTimeline/ActionTimeline.csproj b/ActionTimeline/ActionTimeline.csproj
deleted file mode 100644
index 92d92ac..0000000
--- a/ActionTimeline/ActionTimeline.csproj
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
- x64
- net7.0
- latest
- x64
- Debug;Release
-
-
-
-
- ActionTimeline
- 1.2.0.0
- 1.2.0.0
- 1.2.0.0
-
-
-
-
- Library
- true
- false
- true
- true
- true
- full
- false
- enable
- Nullable
- false
- true
-
-
-
-
- true
-
-
-
-
- dev
- ../dalamud/
- $(APPDATA)\XIVLauncher\addon\Hooks\$(DalamudVersion)\
-
-
-
-
-
- $(AssemblySearchPaths);
- $(DalamudLocal);
- $(DalamudLibPath);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $(DalamudLibPath)FFXIVClientStructs.dll
- false
-
-
- $(DalamudLibPath)Newtonsoft.Json.dll
- false
-
-
- $(DalamudLibPath)Dalamud.dll
- false
-
-
- $(DalamudLibPath)ImGui.NET.dll
- false
-
-
- $(DalamudLibPath)ImGuiScene.dll
- false
-
-
- $(DalamudLibPath)Lumina.dll
- false
-
-
- $(DalamudLibPath)Lumina.Excel.dll
- false
-
-
-
-
-
-
-
-
diff --git a/ActionTimeline/ActionTimeline.json b/ActionTimeline/ActionTimeline.json
deleted file mode 100644
index 6009a21..0000000
--- a/ActionTimeline/ActionTimeline.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "Name": "ActionTimeline",
- "Author": "Tischel",
- "Punchline": "Show your actions in real-time.",
- "Description": "Configurable timeline display of all the actions you use.",
- "RepoUrl": "https://github.com/Tischel/ActionTimeline",
- "Tags": [ "UI" ],
- "Changelog": "# 1.2.0.0\n- Added support for Patch 6.4."
-}
\ No newline at end of file
diff --git a/ActionTimeline/Helpers/DrawHelper.cs b/ActionTimeline/Helpers/DrawHelper.cs
deleted file mode 100644
index cbcb4a7..0000000
--- a/ActionTimeline/Helpers/DrawHelper.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using ImGuiNET;
-using ImGuiScene;
-using System.Numerics;
-
-namespace ActionTimeline.Helpers
-{
- internal static class DrawHelper
- {
- public static void DrawIcon(uint iconId, Vector2 position, Vector2 size, float alpha, ImDrawListPtr drawList)
- {
- TextureWrap? texture = TexturesCache.Instance?.GetTextureFromIconId(iconId);
- if (texture == null) return;
-
- uint color = ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, alpha));
- drawList.AddImage(texture.ImGuiHandle, position, position + size, Vector2.Zero, Vector2.One, color);
- }
-
- public static void SetTooltip(string message)
- {
- if (ImGui.IsItemHovered())
- {
- ImGui.SetTooltip(message);
- }
- }
- }
-}
diff --git a/ActionTimeline/Helpers/TextureLoader.cs b/ActionTimeline/Helpers/TextureLoader.cs
deleted file mode 100644
index 160bfa6..0000000
--- a/ActionTimeline/Helpers/TextureLoader.cs
+++ /dev/null
@@ -1,153 +0,0 @@
-using Dalamud.Logging;
-using Dalamud.Utility;
-using ImGuiScene;
-using Lumina.Data.Files;
-using Lumina.Data.Parsing.Tex;
-using System;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using static Lumina.Data.Files.TexFile;
-
-namespace ActionTimeline.Helpers
-{
- internal static class TextureLoader
- {
- public static TextureWrap? LoadTexture(string path, bool manualLoad)
- {
- if (!manualLoad)
- {
- try
- {
- TexFile? iconFile = Plugin.DataManager.GetFile(path);
- if (iconFile != null)
- {
- return Plugin.UiBuilder.LoadImageRaw(iconFile.GetRgbaImageData(), iconFile.Header.Width, iconFile.Header.Height, 4);
- }
- }
- catch
- {
- return null;
- }
- }
-
- return ManuallyLoadTexture(path);
- }
-
- private static unsafe TextureWrap? ManuallyLoadTexture(string path)
- {
- try
- {
- var fileStream = new FileStream(path, FileMode.Open);
- var reader = new BinaryReader(fileStream);
-
- // read header
- int headerSize = Unsafe.SizeOf();
- ReadOnlySpan headerData = reader.ReadBytes(headerSize);
- TexHeader Header = MemoryMarshal.Read(headerData);
-
- // read image data
- byte[] rawImageData = reader.ReadBytes((int)fileStream.Length - headerSize);
- byte[] imageData = new byte[Header.Width * Header.Height * 4];
-
- if (!ProcessTexture(Header.Format, rawImageData, imageData, Header.Width, Header.Height))
- {
- return null;
- }
-
- return Plugin.UiBuilder.LoadImageRaw(GetRgbaImageData(imageData), Header.Width, Header.Height, 4);
- }
- catch
- {
- PluginLog.Error("Error loading texture: " + path);
- return null;
- }
- }
-
- private static bool ProcessTexture(TextureFormat format, byte[] src, byte[] dst, int width, int height)
- {
- switch (format)
- {
- case TextureFormat.DXT1: Decompress(SquishOptions.DXT1, src, dst, width, height); return true;
- case TextureFormat.DXT3: Decompress(SquishOptions.DXT3, src, dst, width, height); return true;
- case TextureFormat.DXT5: Decompress(SquishOptions.DXT5, src, dst, width, height); return true;
- case TextureFormat.B5G5R5A1: ProcessA1R5G5B5(src, dst, width, height); return true;
- case TextureFormat.B4G4R4A4: ProcessA4R4G4B4(src, dst, width, height); return true;
- case TextureFormat.L8: ProcessR3G3B2(src, dst, width, height); return true;
- case TextureFormat.B8G8R8A8: Array.Copy(src, dst, dst.Length); return true;
- }
-
- return false;
- }
-
- private static void Decompress(SquishOptions squishOptions, byte[] src, byte[] dst, int width, int height)
- {
- var decompressed = Squish.DecompressImage(src, width, height, squishOptions);
- Array.Copy(decompressed, dst, dst.Length);
- }
-
- private static byte[] GetRgbaImageData(byte[] imageData)
- {
- var dst = new byte[imageData.Length];
-
- for (var i = 0; i < dst.Length; i += 4)
- {
- dst[i] = imageData[i + 2];
- dst[i + 1] = imageData[i + 1];
- dst[i + 2] = imageData[i];
- dst[i + 3] = imageData[i + 3];
- }
-
- return dst;
- }
-
- private static void ProcessA1R5G5B5(Span src, byte[] dst, int width, int height)
- {
- for (var i = 0; (i + 2) <= 2 * width * height; i += 2)
- {
- var v = BitConverter.ToUInt16(src.Slice(i, sizeof(UInt16)).ToArray(), 0);
-
- var a = (uint)(v & 0x8000);
- var r = (uint)(v & 0x7C00);
- var g = (uint)(v & 0x03E0);
- var b = (uint)(v & 0x001F);
-
- var rgb = ((r << 9) | (g << 6) | (b << 3));
- var argbValue = (a * 0x1FE00 | rgb | ((rgb >> 5) & 0x070707));
-
- for (var j = 0; j < 4; ++j)
- {
- dst[i * 2 + j] = (byte)(argbValue >> (8 * j));
- }
- }
- }
-
- private static void ProcessA4R4G4B4(Span src, byte[] dst, int width, int height)
- {
- for (var i = 0; (i + 2) <= 2 * width * height; i += 2)
- {
- var v = BitConverter.ToUInt16(src.Slice(i, sizeof(UInt16)).ToArray(), 0);
-
- for (var j = 0; j < 4; ++j)
- {
- dst[i * 2 + j] = (byte)(((v >> (4 * j)) & 0x0F) << 4);
- }
- }
- }
-
- private static void ProcessR3G3B2(Span src, byte[] dst, int width, int height)
- {
- for (var i = 0; i < width * height; ++i)
- {
- var r = (uint)(src[i] & 0xE0);
- var g = (uint)(src[i] & 0x1C);
- var b = (uint)(src[i] & 0x03);
-
- dst[i * 4 + 0] = (byte)(b | (b << 2) | (b << 4) | (b << 6));
- dst[i * 4 + 1] = (byte)(g | (g << 3) | (g << 6));
- dst[i * 4 + 2] = (byte)(r | (r << 3) | (r << 6));
- dst[i * 4 + 3] = 0xFF;
- }
- }
- }
-}
diff --git a/ActionTimeline/Helpers/TexturesCache.cs b/ActionTimeline/Helpers/TexturesCache.cs
deleted file mode 100644
index c0db78a..0000000
--- a/ActionTimeline/Helpers/TexturesCache.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-using Dalamud.Plugin.Ipc;
-using ImGuiScene;
-using Lumina.Excel;
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace ActionTimeline.Helpers
-{
- public class TexturesCache : IDisposable
- {
- private Dictionary _cache = new();
-
- public TextureWrap? GetTexture(uint rowId, bool highQuality = false, uint stackCount = 0) where T : ExcelRow
- {
- var sheet = Plugin.DataManager.GetExcelSheet();
-
- return sheet == null ? null : GetTexture(sheet.GetRow(rowId), highQuality, stackCount);
- }
-
- public TextureWrap? GetTexture(dynamic? row, bool highQuality = false, uint stackCount = 0) where T : ExcelRow
- {
- if (row == null)
- {
- return null;
- }
-
- var iconId = row.Icon;
- return GetTextureFromIconId(iconId, highQuality, stackCount);
- }
-
- public TextureWrap? GetTextureFromIconId(uint iconId, bool highQuality = false, uint stackCount = 0)
- {
- if (_cache.TryGetValue(iconId + stackCount, out var texture))
- {
- return texture;
- }
-
- var newTexture = LoadTexture(iconId + stackCount, highQuality);
- if (newTexture == null)
- {
- return null;
- }
-
- _cache.Add(iconId + stackCount, newTexture);
-
- return newTexture;
- }
-
- private unsafe TextureWrap? LoadTexture(uint id, bool highQuality)
- {
- var hqText = highQuality ? "hq/" : "";
- var path = $"ui/icon/{id / 1000 * 1000:000000}/{hqText}{id:000000}_hr1.tex";
-
- try
- {
- var resolvedPath = _penumbraPathResolver.InvokeFunc(path);
-
- if (resolvedPath != null && resolvedPath != path)
- {
- return TextureLoader.LoadTexture(resolvedPath, true);
- }
- }
- catch { }
-
- return TextureLoader.LoadTexture(path, false);
- }
-
- private void RemoveTexture(uint rowId) where T : ExcelRow
- {
- var sheet = Plugin.DataManager.GetExcelSheet();
-
- if (sheet == null)
- {
- return;
- }
-
- RemoveTexture(sheet.GetRow(rowId));
- }
-
- public void RemoveTexture(dynamic? row) where T : ExcelRow
- {
- if (row == null || row?.Icon == null)
- {
- return;
- }
-
- var iconId = row!.Icon;
- RemoveTexture(iconId);
- }
-
- public void RemoveTexture(uint iconId)
- {
- if (_cache.ContainsKey(iconId))
- {
- _cache.Remove(iconId);
- }
- }
-
- public void Clear() => _cache.Clear();
-
- #region singleton
- private ICallGateSubscriber _penumbraPathResolver;
- public static void Initialize() { Instance = new TexturesCache(); }
-
- public static TexturesCache Instance { get; private set; } = null!;
-
- public TexturesCache()
- {
- _penumbraPathResolver = Plugin.PluginInterface.GetIpcSubscriber("Penumbra.ResolveInterfacePath");
- }
-
- ~TexturesCache()
- {
- Dispose(false);
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected void Dispose(bool disposing)
- {
- if (!disposing)
- {
- return;
- }
-
- foreach (var key in _cache.Keys)
- {
- var tex = _cache[key];
- tex?.Dispose();
- }
-
- _cache.Clear();
- }
- #endregion
- }
-}
diff --git a/ActionTimeline/Helpers/TimelineManager.cs b/ActionTimeline/Helpers/TimelineManager.cs
deleted file mode 100644
index d652ee6..0000000
--- a/ActionTimeline/Helpers/TimelineManager.cs
+++ /dev/null
@@ -1,429 +0,0 @@
-using Dalamud.Game;
-using Dalamud.Game.ClientState.Conditions;
-using Dalamud.Game.ClientState.Objects.SubKinds;
-using Dalamud.Hooking;
-using Dalamud.Logging;
-using FFXIVClientStructs.FFXIV.Client.Game;
-using ImGuiNET;
-using Lumina.Excel;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.InteropServices;
-using LuminaAction = Lumina.Excel.GeneratedSheets.Action;
-
-namespace ActionTimeline.Helpers
-{
- public enum TimelineItemType
- {
- Action = 0,
- CastStart = 1,
- CastCancel = 2,
- OffGCD = 3,
- AutoAttack = 4
- }
-
- public class TimelineItem
- {
- public uint ActionID { get; }
- public uint IconID { get; }
- public TimelineItemType Type { get; }
- public double Time { get; }
-
- public float GCDDuration { get; }
- public float CastTime { get; }
-
- public GCDClipData? GCDClipData = null;
-
- public TimelineItem(uint actionID, uint iconID, TimelineItemType type, double time)
- {
- ActionID = actionID;
- IconID = iconID;
- Type = type;
- Time = time;
- GCDDuration = 0;
- CastTime = 0;
- }
-
- public TimelineItem(uint actionID, uint iconID, TimelineItemType type, double time, float gcdDuration, float castTime) : this(actionID, iconID, type, time)
- {
- GCDDuration = gcdDuration;
- CastTime = castTime;
- }
- }
-
- public struct GCDClipData
- {
- public bool IsClipped { get; }
- public double StartTime { get; }
- public double? EndTime { get; }
- public bool IsFakeEndTime { get; }
-
- public bool ShouldDraw => IsClipped && !Utils.UnderThreshold(StartTime, EndTime.HasValue ? EndTime.Value : ImGui.GetTime());
-
- public GCDClipData(bool isClipped, double startTime, double? endTime, bool isFakeEndTime)
- {
- IsClipped = isClipped;
- StartTime = startTime;
- EndTime = endTime;
- IsFakeEndTime = isFakeEndTime;
- }
- }
-
- public class TimelineManager
- {
- #region singleton
- public static void Initialize() { Instance = new TimelineManager(); }
-
- public static TimelineManager Instance { get; private set; } = null!;
-
- public TimelineManager()
- {
- _sheet = Plugin.DataManager.GetExcelSheet();
-
- try
- {
- IntPtr funcPtr = Plugin.SigScanner.ScanText("40 55 53 57 41 54 41 55 41 56 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 45 70");
- _onActionUsedHook = Hook.FromAddress(funcPtr, OnActionUsed);
- _onActionUsedHook?.Enable();
-
- _onActorControlHook = Hook.FromAddress(Plugin.SigScanner.ScanText("E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64"), OnActorControl);
- _onActorControlHook?.Enable();
-
- _onCastHook = Hook.FromAddress(Plugin.SigScanner.ScanText("40 55 56 48 81 EC ?? ?? ?? ?? 48 8B EA"), OnCast);
- _onCastHook?.Enable();
- }
- catch (Exception e)
- {
- PluginLog.Error("Error initiating hooks: " + e.Message);
- }
-
- Plugin.Framework.Update += Update;
- }
-
- ~TimelineManager()
- {
- Dispose(false);
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected void Dispose(bool disposing)
- {
- if (!disposing)
- {
- return;
- }
-
- Plugin.Framework.Update -= Update;
-
- _items.Clear();
-
- _onActionUsedHook?.Disable();
- _onActionUsedHook?.Dispose();
-
- _onActorControlHook?.Disable();
- _onActorControlHook?.Dispose();
-
- _onCastHook?.Disable();
- _onCastHook?.Dispose();
- }
- #endregion
-
- private delegate void OnActionUsedDelegate(uint sourceId, IntPtr sourceCharacter, IntPtr pos, IntPtr effectHeader, IntPtr effectArray, IntPtr effectTrail);
- private Hook? _onActionUsedHook;
-
- private delegate void OnActorControlDelegate(uint entityId, uint id, uint unk1, uint type, uint unk2, uint unk3, uint unk4, uint unk5, UInt64 targetId, byte unk6);
- private Hook? _onActorControlHook;
-
- private delegate void OnCastDelegate(uint sourceId, IntPtr sourceCharacter);
- private Hook? _onCastHook;
-
- private ExcelSheet? _sheet;
- private Dictionary _specialCasesMap = new()
- {
- // MNK
- [16475] = 53, // anatman
-
- // SAM
- [16484] = 7477, // kaeshi higanbana
- [16485] = 7477, // kaeshi goken
- [16486] = 7477, // keashi setsugekka
-
- // RDM
- [25858] = 7504 // resolution
- };
- private Dictionary _hardcodedCasesMap = new()
- {
- // NIN
- [2259] = 0.5f, // ten
- [2261] = 0.5f, // chi
- [2263] = 0.5f, // jin
- [18805] = 0.5f, // ten
- [18806] = 0.5f, // chi
- [18807] = 0.5f, // jin
- [2265] = 1.5f, // fuma shuriken
- [2266] = 1.5f, // katon
- [2267] = 1.5f, // raiton
- [2268] = 1.5f, // hyoton
- [2269] = 1.5f, // huton
- [2270] = 1.5f, // doton
- [2271] = 1.5f, // suiton
- [2272] = 1.5f, // rabbit medium
- [16491] = 1.5f, // goka mekkyaku
- [16492] = 1.5f, // hyosho ranryu
- };
-
- private static int kMaxItemCount = 50;
- private List _items = new List(kMaxItemCount);
- public IReadOnlyCollection Items => _items.AsReadOnly();
-
- private Settings Settings => Plugin.Settings;
-
- private double _outOfCombatStartTime = -1;
- private bool _hadSwiftcast = false;
-
-
- private unsafe void Update(Framework framework)
- {
- double now = ImGui.GetTime();
-
- CheckOutOfCombat(now);
- CheckSwiftcast();
-
- // gcd clipping logic
- for (int i = 0; i < _items.Count; i++)
- {
- TimelineItem item = _items[i];
- if (item.Type != TimelineItemType.Action) { continue; }
- if (item.GCDDuration == 0) { continue; } // does this ever happen???
- if (item.GCDClipData.HasValue && item.GCDClipData.Value.EndTime.HasValue && !item.GCDClipData.Value.IsFakeEndTime) { continue; }
-
- double gcdClipStart = item.Time + Math.Max(0, item.GCDDuration - item.CastTime);
-
- // cast threshold
- if (item.CastTime > 0)
- {
- gcdClipStart += Settings.GCDClippingCastsThreshold;
- }
-
- // check if clipped
- if (now >= gcdClipStart)
- {
- var (gcdClipEnd, isFakeEnd) = FindGCDClipEndTime(item, i);
-
- // make sure threshold doesn't break the math
- if (gcdClipEnd.HasValue && gcdClipStart > gcdClipEnd.Value)
- {
- gcdClipStart = gcdClipEnd.Value;
- }
-
- // check max time
- if (!gcdClipEnd.HasValue && now - gcdClipStart > Settings.GCDClippingMaxTime)
- {
- gcdClipEnd = now;
- isFakeEnd = false;
- }
-
- // not clipped?
- if (!isFakeEnd && gcdClipEnd.HasValue && Utils.UnderThreshold(gcdClipStart, gcdClipEnd.Value))
- {
- item.GCDClipData = new GCDClipData(false, 0, 0, false);
- }
- // clipped :(
- else
- {
- item.GCDClipData = new GCDClipData(true, gcdClipStart, gcdClipEnd, isFakeEnd);
- }
- }
- }
- }
-
- private void CheckOutOfCombat(double now)
- {
- if (!Plugin.Condition[ConditionFlag.InCombat] && _outOfCombatStartTime != -2)
- {
- if (_outOfCombatStartTime == -1)
- {
- _outOfCombatStartTime = now;
- }
- else if (now - _outOfCombatStartTime >= Settings.OutOfCombatClearTime)
- {
- _items.Clear();
- _outOfCombatStartTime = -2;
- }
- }
- else
- {
- _outOfCombatStartTime = -1;
- }
- }
-
- private void CheckSwiftcast()
- {
- PlayerCharacter? player = Plugin.ClientState.LocalPlayer;
- if (player != null)
- {
- _hadSwiftcast = player.StatusList.Any(s => s.StatusId == 167);
- }
- }
-
- private (double?, bool) FindGCDClipEndTime(TimelineItem item, int index)
- {
- if (index >= _items.Count - 1) { return (null, false); }
-
- TimelineItem? prevItem = null;
-
- for (int i = index + 1; i < _items.Count; i++)
- {
- TimelineItem nextItem = _items[i];
- if (nextItem.Type == TimelineItemType.Action)
- {
- double time = prevItem != null && prevItem.Type == TimelineItemType.CastStart ? prevItem.Time : nextItem.Time;
- return (time, false);
- }
- else if (nextItem.Type == TimelineItemType.CastStart && i == _items.Count - 1)
- {
- return (nextItem.Time, true);
- }
-
- prevItem = nextItem;
- }
-
- return (null, false);
- }
-
- private unsafe float GetGCDTime(uint actionId)
- {
- ActionManager* actionManager = ActionManager.Instance();
- uint adjustedId = actionManager->GetAdjustedActionId(actionId);
- return actionManager->GetRecastTime(ActionType.Spell, adjustedId);
- }
-
- private unsafe float GetCastTime(uint actionId)
- {
- ActionManager* actionManager = ActionManager.Instance();
- uint adjustedId = actionManager->GetAdjustedActionId(actionId);
- return (float)ActionManager.GetAdjustedCastTime(ActionType.Spell, adjustedId) / 1000f;
- }
-
- private void AddItem(uint actionId, TimelineItemType type)
- {
- LuminaAction? action = _sheet?.GetRow(actionId);
- if (action == null) { return; }
-
- // only cache the last kMaxItemCount items
- if (_items.Count >= kMaxItemCount)
- {
- _items.RemoveAt(0);
- }
-
- double now = ImGui.GetTime();
- float gcdDuration = 0;
- float castTime = 0;
-
- // handle sprint and auto attack icons
- int iconId = actionId == 3 ? 104 : (actionId == 1 ? 101 : action.Icon);
-
- // handle weird cases
- uint id = actionId;
- if (_specialCasesMap.TryGetValue(actionId, out uint replacedId))
- {
- type = TimelineItemType.Action;
- id = replacedId;
- }
-
- // calculate gcd and cast time
- if (type == TimelineItemType.CastStart)
- {
- gcdDuration = GetGCDTime(id);
- castTime = GetCastTime(id);
- }
- else if (type == TimelineItemType.Action)
- {
- TimelineItem? lastItem = _items.LastOrDefault();
- if (lastItem != null && lastItem.Type == TimelineItemType.CastStart)
- {
- gcdDuration = lastItem.GCDDuration;
- castTime = lastItem.CastTime;
- }
- else
- {
- gcdDuration = GetGCDTime(id);
- castTime = _hadSwiftcast ? 0 : GetCastTime(id);
- }
- }
-
- // handle more weird cases
- if (_hardcodedCasesMap.TryGetValue(actionId, out float gcd))
- {
- type = TimelineItemType.Action;
- gcdDuration = gcd;
- }
-
- TimelineItem item = new TimelineItem(actionId, (uint)iconId, type, now, gcdDuration, castTime);
- _items.Add(item);
- }
-
- private TimelineItemType? TypeForActionID(uint actionId)
- {
- LuminaAction? action = _sheet?.GetRow(actionId);
- if (action == null) { return null; }
-
- // off gcd or sprint
- if (action.ActionCategory.Row is 4 || actionId == 3)
- {
- return TimelineItemType.OffGCD;
- }
-
- if (action.ActionCategory.Row is 1)
- {
- return TimelineItemType.AutoAttack;
- }
-
- return TimelineItemType.Action;
- }
-
- private void OnActionUsed(uint sourceId, IntPtr sourceCharacter, IntPtr pos, IntPtr effectHeader,
- IntPtr effectArray, IntPtr effectTrail)
- {
- _onActionUsedHook?.Original(sourceId, sourceCharacter, pos, effectHeader, effectArray, effectTrail);
-
- PlayerCharacter? player = Plugin.ClientState.LocalPlayer;
- if (player == null || sourceId != player.ObjectId) { return; }
-
- int actionId = Marshal.ReadInt32(effectHeader, 0x8);
- TimelineItemType? type = TypeForActionID((uint)actionId);
- if (!type.HasValue) { return; }
-
- AddItem((uint)actionId, type.Value);
- }
-
- private void OnActorControl(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg4, uint arg5, ulong targetId, byte a10)
- {
- _onActorControlHook?.Original(entityId, type, buffID, direct, actionId, sourceId, arg4, arg5, targetId, a10);
-
- if (type != 15) { return; }
-
- PlayerCharacter? player = Plugin.ClientState.LocalPlayer;
- if (player == null || entityId != player.ObjectId) { return; }
-
- AddItem(actionId, TimelineItemType.CastCancel);
- }
-
- private void OnCast(uint sourceId, IntPtr ptr)
- {
- _onCastHook?.Original(sourceId, ptr);
-
- PlayerCharacter? player = Plugin.ClientState.LocalPlayer;
- if (player == null || sourceId != player.ObjectId) { return; }
-
- short actionId = Marshal.ReadInt16(ptr);
- AddItem((uint)actionId, TimelineItemType.CastStart);
- }
- }
-}
diff --git a/ActionTimeline/Helpers/Utils.cs b/ActionTimeline/Helpers/Utils.cs
deleted file mode 100644
index 796b93a..0000000
--- a/ActionTimeline/Helpers/Utils.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace ActionTimeline.Helpers
-{
- public static class Utils
- {
- public static bool UnderThreshold(double start, double end)
- {
- return Math.Abs(end - start) < Plugin.Settings.GCDClippingThreshold;
- }
-
- }
-}
diff --git a/ActionTimeline/Media/icon.png b/ActionTimeline/Media/icon.png
deleted file mode 100644
index 4013719..0000000
Binary files a/ActionTimeline/Media/icon.png and /dev/null differ
diff --git a/ActionTimeline/Plugin.cs b/ActionTimeline/Plugin.cs
deleted file mode 100644
index 9563244..0000000
--- a/ActionTimeline/Plugin.cs
+++ /dev/null
@@ -1,245 +0,0 @@
-using ActionTimeline.Helpers;
-using ActionTimeline.Windows;
-using Dalamud.Data;
-using Dalamud.Game;
-using Dalamud.Game.ClientState;
-using Dalamud.Game.ClientState.Conditions;
-using Dalamud.Game.ClientState.Keys;
-using Dalamud.Game.Command;
-using Dalamud.Game.Gui;
-using Dalamud.Interface;
-using Dalamud.Interface.Windowing;
-using Dalamud.Plugin;
-using System;
-using System.Reflection;
-using SigScanner = Dalamud.Game.SigScanner;
-
-namespace ActionTimeline
-{
- public class Plugin : IDalamudPlugin
- {
- public static ClientState ClientState { get; private set; } = null!;
- public static CommandManager CommandManager { get; private set; } = null!;
- public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
- public static Condition Condition { get; private set; } = null!;
- public static DataManager DataManager { get; private set; } = null!;
- public static Framework Framework { get; private set; } = null!;
- public static GameGui GameGui { get; private set; } = null!;
- public static SigScanner SigScanner { get; private set; } = null!;
- public static UiBuilder UiBuilder { get; private set; } = null!;
- public static KeyState KeyState { get; private set; } = null!;
-
- public static string AssemblyLocation { get; private set; } = "";
- public string Name => "ActionTimeline";
-
- public static string Version { get; private set; } = "";
-
- public static Settings Settings { get; private set; } = null!;
-
- private static WindowSystem _windowSystem = null!;
- private static SettingsWindow _settingsWindow = null!;
- private static TimelineSettingsWindow _timelineSettingsWindow = null!;
- private static RotationSettingsWindow _rotationSettingsWindow = null!;
- private static TimelineWindow _timelineWindow = null!;
- private static RotationWindow _rotationWindow = null!;
-
-
- public Plugin(
- ClientState clientState,
- CommandManager commandManager,
- DalamudPluginInterface pluginInterface,
- Condition condition,
- DataManager dataManager,
- Framework framework,
- GameGui gameGui,
- SigScanner sigScanner,
- KeyState keyState
- )
- {
- ClientState = clientState;
- CommandManager = commandManager;
- PluginInterface = pluginInterface;
- Condition = condition;
- DataManager = dataManager;
- Framework = framework;
- GameGui = gameGui;
- SigScanner = sigScanner;
- UiBuilder = PluginInterface.UiBuilder;
- KeyState = keyState;
-
- if (pluginInterface.AssemblyLocation.DirectoryName != null)
- {
- AssemblyLocation = pluginInterface.AssemblyLocation.DirectoryName + "\\";
- }
- else
- {
- AssemblyLocation = Assembly.GetExecutingAssembly().Location;
- }
-
- Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "1.2.0.0";
-
- UiBuilder.Draw += Draw;
- UiBuilder.OpenConfigUi += OpenConfigUi;
-
- CommandManager.AddHandler(
- "/actiontimeline",
- new CommandInfo(PluginCommand)
- {
- HelpMessage = "Opens the ActionTimeline configuration window.",
-
- ShowInHelp = true
- }
- );
-
- CommandManager.AddHandler(
- "/att",
- new CommandInfo(PluginCommand)
- {
- HelpMessage = "Opens the ActionTimeline Timeline Settings window.",
-
- ShowInHelp = true
- }
- );
-
- CommandManager.AddHandler(
- "/atr",
- new CommandInfo(PluginCommand)
- {
- HelpMessage = "Opens the ActionTimeline Rotation Settings window.",
-
- ShowInHelp = true
- }
- );
-
- TexturesCache.Initialize();
- TimelineManager.Initialize();
-
- Settings = Settings.Load();
-
- CreateWindows();
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- private void PluginCommand(string command, string arguments)
- {
- if (command == "/att")
- {
- _timelineSettingsWindow.IsOpen = !_timelineSettingsWindow.IsOpen;
- }
- else if (command == "/atr")
- {
- _rotationSettingsWindow.IsOpen = !_rotationSettingsWindow.IsOpen;
- }
- else
- {
- _settingsWindow.IsOpen = !_settingsWindow.IsOpen;
- }
- }
-
- private void CreateWindows()
- {
- _settingsWindow = new SettingsWindow("ActionTimeline v"+ Version);
- _timelineSettingsWindow = new TimelineSettingsWindow("Timeline Settings");
- _rotationSettingsWindow = new RotationSettingsWindow("Rotation Settings");
- _timelineWindow = new TimelineWindow("Timeline");
- _rotationWindow = new RotationWindow("Rotation");
-
- _windowSystem = new WindowSystem("ActionTimeline_Windows");
- _windowSystem.AddWindow(_settingsWindow);
- _windowSystem.AddWindow(_timelineSettingsWindow);
- _windowSystem.AddWindow(_rotationSettingsWindow);
- _windowSystem.AddWindow(_timelineWindow);
- _windowSystem.AddWindow(_rotationWindow);
- }
-
- private void Draw()
- {
- if (Settings == null || ClientState.LocalPlayer == null) return;
-
- UpdateTimeline();
- UpdateRotation();
-
- _windowSystem?.Draw();
- }
-
- public static void ShowTimelineSettingsWindow()
- {
- _timelineSettingsWindow.IsOpen = true;
- }
-
- public static void ShowRotationSettingsWindow()
- {
- _rotationSettingsWindow.IsOpen = true;
- }
-
- private void UpdateTimeline()
- {
- bool show = Settings.ShowTimeline;
- if (show)
- {
- if (Settings.ShowTimelineOnlyInCombat && !Condition[ConditionFlag.InCombat])
- {
- show = false;
- }
-
- if (Settings.ShowTimelineOnlyInDuty && !Condition[ConditionFlag.BoundByDuty])
- {
- show = false;
- }
- }
-
- _timelineWindow.IsOpen = show;
- }
-
- private void UpdateRotation()
- {
- bool show = Settings.ShowRotation;
- if (show)
- {
- if (Settings.ShowRotationOnlyInCombat && !Condition[ConditionFlag.InCombat])
- {
- show = false;
- }
-
- if (Settings.ShowRotationOnlyInDuty && !Condition[ConditionFlag.BoundByDuty])
- {
- show = false;
- }
- }
-
- _rotationWindow.IsOpen = show;
- }
-
- private void OpenConfigUi()
- {
- _settingsWindow.IsOpen = true;
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (!disposing)
- {
- return;
- }
-
- Settings.Save(Settings);
-
- TexturesCache.Instance?.Dispose();
- TimelineManager.Instance?.Dispose();
-
- _windowSystem.RemoveAllWindows();
-
- CommandManager.RemoveHandler("/actiontimeline");
- CommandManager.RemoveHandler("/att");
- CommandManager.RemoveHandler("/atr");
-
- UiBuilder.Draw -= Draw;
- UiBuilder.OpenConfigUi -= OpenConfigUi;
- UiBuilder.RebuildFonts();
- }
- }
-}
diff --git a/ActionTimeline/Settings.cs b/ActionTimeline/Settings.cs
deleted file mode 100644
index 1efc8c2..0000000
--- a/ActionTimeline/Settings.cs
+++ /dev/null
@@ -1,121 +0,0 @@
-using Dalamud.Logging;
-using Newtonsoft.Json;
-using System;
-using System.IO;
-using System.Numerics;
-
-namespace ActionTimeline
-{
- public class Settings
- {
- public bool ShowTimeline = true;
- public bool TimelineLocked = false;
- public int OutOfCombatClearTime = 10;
- public bool ShowTimelineOnlyInDuty = false;
- public bool ShowTimelineOnlyInCombat = false;
- public int TimelineTime = 5; // seconds
-
- public Vector4 TimelineLockedBackgroundColor = new Vector4(0f, 0f, 0f, 0.5f);
- public Vector4 TimelineUnlockedBackgroundColor = new Vector4(0f, 0f, 0f, 0.75f);
-
- public int TimelineIconSize = 40;
-
- public int TimelineOffGCDIconSize = 30;
- public int TimelineOffGCDOffset = -5;
-
- public bool TimelineShowAutoAttacks = false;
- public int TimelineAutoAttackSize = 20;
- public int TimelineAutoAttackOffset = 10;
-
- public Vector4 CastInProgressColor = new Vector4(0.5f, 0.5f, 0.5f, 1f);
- public Vector4 CastFinishedColor = new Vector4(0.2f, 0.8f, 0.2f, 1f);
- public Vector4 CastCanceledColor = new Vector4(0.8f, 0.2f, 0.2f, 1f);
-
- public bool ShowGrid = true;
- public bool ShowGridCenterLine = false;
- public bool GridDivideBySeconds = true;
- public bool GridShowSecondsText = true;
- public bool GridSubdivideSeconds = true;
- public int GridSubdivisionCount = 2;
- public int GridLineWidth = 1;
- public int GridSubdivisionLineWidth = 1;
- public Vector4 GridLineColor = new Vector4(0.3f, 0.3f, 0.3f, 1f);
- public Vector4 GridSubdivisionLineColor = new Vector4(0.3f, 0.3f, 0.3f, 0.2f);
-
- public bool ShowGCDClipping = true;
- public float GCDClippingThreshold = 0.1f; // seconds
- public float GCDClippingCastsThreshold = 0.6f; // seconds
- public int GCDClippingMaxTime = 5; // seconds
- public Vector4 GCDClippingColor = new Vector4(1f, 0.2f, 0.2f, 0.3f);
-
- public bool ShowRotation = true;
- public bool RotationLocked = false;
- public bool ShowRotationOnlyInDuty = false;
- public bool ShowRotationOnlyInCombat = false;
-
- public int RotationGCDSpacing = 20;
- public int RotationOffGCDSpacing = 5;
-
- public bool RotationSeparatorEnabled = true;
- public int RotationSeparatorTime = 10; // seconds
- public int RotationSeparatorWidth = 3;
- public Vector4 RotationSeparatorColor = new Vector4(0.3f, 0.3f, 0.3f, 1f);
-
- public Vector4 RotationLockedBackgroundColor = new Vector4(0f, 0f, 0f, 0.5f);
- public Vector4 RotationUnlockedBackgroundColor = new Vector4(0f, 0f, 0f, 0.75f);
-
- public int RotationIconSize = 40;
-
- public int RotationOffGCDIconSize = 30;
- public int RotationOffGCDOffset = -5;
-
- #region load / save
- private static string JsonPath = Path.Combine(Plugin.PluginInterface.GetPluginConfigDirectory(), "Settings.json");
- public static Settings Load()
- {
- string path = JsonPath;
- Settings? settings = null;
-
- try
- {
- if (File.Exists(path))
- {
- string jsonString = File.ReadAllText(path);
- settings = JsonConvert.DeserializeObject(jsonString);
- }
- }
- catch (Exception e)
- {
- PluginLog.Error("Error reading settings file: " + e.Message);
- }
-
- if (settings == null)
- {
- settings = new Settings();
- Save(settings);
- }
-
- return settings;
- }
-
- public static void Save(Settings settings)
- {
- try
- {
- JsonSerializerSettings serializerSettings = new JsonSerializerSettings
- {
- TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
- TypeNameHandling = TypeNameHandling.Objects
- };
- string jsonString = JsonConvert.SerializeObject(settings, Formatting.Indented, serializerSettings);
-
- File.WriteAllText(JsonPath, jsonString);
- }
- catch (Exception e)
- {
- PluginLog.Error("Error saving settings file: " + e.Message);
- }
- }
- #endregion
- }
-}
diff --git a/ActionTimeline/Windows/RotationSettingsWindow.cs b/ActionTimeline/Windows/RotationSettingsWindow.cs
deleted file mode 100644
index cb76901..0000000
--- a/ActionTimeline/Windows/RotationSettingsWindow.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using ActionTimeline.Helpers;
-using Dalamud.Interface;
-using Dalamud.Interface.Windowing;
-using ImGuiNET;
-using System.Numerics;
-
-namespace ActionTimeline.Windows
-{
- public class RotationSettingsWindow : Window
- {
- private float _scale => ImGuiHelpers.GlobalScale;
- private Settings Settings => Plugin.Settings;
-
- public RotationSettingsWindow(string name) : base(name)
- {
- Flags = ImGuiWindowFlags.NoScrollbar
- | ImGuiWindowFlags.NoCollapse
- | ImGuiWindowFlags.NoResize
- | ImGuiWindowFlags.NoScrollWithMouse;
-
- Size = new Vector2(300, 350);
- }
-
- public override void Draw()
- {
- if (!ImGui.BeginTabBar("##Rotation_Settings_TabBar"))
- {
- return;
- }
-
- ImGui.PushItemWidth(80 * _scale);
-
- // general
- if (ImGui.BeginTabItem("General##Rotation_General"))
- {
- DrawGeneralTab();
- ImGui.EndTabItem();
- }
-
- // icons
- if (ImGui.BeginTabItem("Icons##Rotation_Icons"))
- {
- DrawIconsTab();
- ImGui.EndTabItem();
- }
-
- // separator
- if (ImGui.BeginTabItem("Separator##Rotation_Separator"))
- {
- DrawSeparatorTab();
- ImGui.EndTabItem();
- }
-
- ImGui.EndTabBar();
- }
-
- public void DrawGeneralTab()
- {
- ImGui.Checkbox("Enabled", ref Settings.ShowRotation);
-
- if (!Settings.ShowRotation) { return; }
-
- ImGui.DragInt("GCD Spacing", ref Settings.RotationGCDSpacing);
- ImGui.DragInt("Off-GCD Spacing", ref Settings.RotationOffGCDSpacing);
-
- ImGui.NewLine();
- ImGui.Checkbox("Locked", ref Settings.RotationLocked);
- ImGui.ColorEdit4("Locked Color", ref Settings.RotationLockedBackgroundColor, ImGuiColorEditFlags.NoInputs);
- ImGui.ColorEdit4("Unlocked Color", ref Settings.RotationUnlockedBackgroundColor, ImGuiColorEditFlags.NoInputs);
-
- ImGui.NewLine();
- ImGui.DragInt("Out of Combat Clear Time (seconds)", ref Settings.OutOfCombatClearTime, 0.1f, 1, 30);
- DrawHelper.SetTooltip("The rotation will be cleared after being out of combat for this many seconds.");
-
- ImGui.Checkbox("Show Only In Duty", ref Settings.ShowRotationOnlyInDuty);
- ImGui.Checkbox("Show Only In Combat", ref Settings.ShowRotationOnlyInCombat);
- }
-
- public void DrawIconsTab()
- {
- ImGui.DragInt("Icon Size", ref Settings.RotationIconSize);
-
- ImGui.NewLine();
- ImGui.DragInt("Off GCD Icon Size", ref Settings.RotationOffGCDIconSize);
- ImGui.DragInt("Iff GCD Vertical Offset", ref Settings.RotationOffGCDOffset);
- }
-
- public void DrawSeparatorTab()
- {
- ImGui.Checkbox("Enabled", ref Settings.RotationSeparatorEnabled);
- DrawHelper.SetTooltip("Draws a separator between 2 abilities if enough time has passed in between.");
-
- if (!Settings.RotationSeparatorEnabled) { return; }
-
- ImGui.DragInt("Time (seconds)", ref Settings.RotationSeparatorTime, 0.5f, 5, 60);
- ImGui.DragInt("Width", ref Settings.RotationSeparatorWidth, 0.5f, 1, 10);
- ImGui.ColorEdit4("Color", ref Settings.RotationSeparatorColor, ImGuiColorEditFlags.NoInputs);
- }
- }
-}
diff --git a/ActionTimeline/Windows/RotationWindow.cs b/ActionTimeline/Windows/RotationWindow.cs
deleted file mode 100644
index 7fddc33..0000000
--- a/ActionTimeline/Windows/RotationWindow.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using ActionTimeline.Helpers;
-using Dalamud.Interface;
-using Dalamud.Interface.Windowing;
-using Dalamud.Logging;
-using ImGuiNET;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-
-namespace ActionTimeline.Windows
-{
- internal class RotationWindow : Window
- {
- private float _scale => ImGuiHelpers.GlobalScale;
- private Settings Settings => Plugin.Settings;
-
- private ImGuiWindowFlags _baseFlags = ImGuiWindowFlags.NoScrollbar
- | ImGuiWindowFlags.NoCollapse
- | ImGuiWindowFlags.NoTitleBar
- | ImGuiWindowFlags.NoNav
- | ImGuiWindowFlags.NoScrollWithMouse;
-
- public RotationWindow(string name) : base(name)
- {
- Flags = _baseFlags;
-
- Size = new Vector2(560, 90);
- SizeCondition = ImGuiCond.FirstUseEver;
-
- Position = new Vector2(200, 295);
- PositionCondition = ImGuiCond.FirstUseEver;
- }
-
- public override void PreDraw()
- {
- Vector4 bgColor = Settings.RotationLocked ? Settings.RotationLockedBackgroundColor : Settings.RotationUnlockedBackgroundColor;
- ImGui.PushStyleColor(ImGuiCol.WindowBg, bgColor);
-
- Flags = _baseFlags;
-
- if (Settings.RotationLocked)
- {
- Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs;
- }
-
- ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
- ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0);
- }
-
- public override void PostDraw()
- {
- ImGui.PopStyleColor();
- ImGui.PopStyleVar(2);
- }
-
- public override void Draw()
- {
- if (ImGui.IsWindowHovered())
- {
- if (ImGui.IsMouseClicked(ImGuiMouseButton.Right))
- {
- Plugin.ShowRotationSettingsWindow();
- }
- }
-
- IReadOnlyCollection? list = TimelineManager.Instance?.Items;
- if (list == null) { return; }
-
- ImDrawListPtr drawList = ImGui.GetWindowDrawList();
- Vector2 pos = ImGui.GetWindowPos();
- float width = ImGui.GetWindowWidth();
- float height = ImGui.GetWindowHeight();
- float posX = width - 5;
-
- Vector2 regularSize = new Vector2(Settings.RotationIconSize);
- Vector2 offGCDSize = new Vector2(Settings.RotationOffGCDIconSize);
-
- uint separatorColor = ImGui.ColorConvertFloat4ToU32(Settings.RotationSeparatorColor);
-
- TimelineItem? lastValidItem = null;
-
- for (int i = list.Count - 1; i >= 0; i--)
- {
- if (posX < -50) { break; }
-
- TimelineItem item = list.ElementAt(i);
- if (item.Type != TimelineItemType.Action && item.Type != TimelineItemType.OffGCD) { continue; }
-
- // spacing
- if (lastValidItem != null)
- {
- int spacing = (lastValidItem.Type == TimelineItemType.Action ? Settings.RotationGCDSpacing : Settings.RotationOffGCDSpacing);
- posX -= spacing;
-
- double timeDiff = Math.Abs(item.Time - lastValidItem.Time);
- if (Settings.RotationSeparatorEnabled && timeDiff > Settings.RotationSeparatorTime)
- {
- posX -= Math.Min(1, (int)Settings.RotationSeparatorWidth / 2);
-
- Vector2 separatorPosition = new Vector2(pos.X + posX, pos.Y);
- Vector2 separatorSize = new Vector2(Settings.RotationSeparatorWidth, height);
- drawList.AddRectFilled(separatorPosition, separatorPosition + separatorSize, separatorColor);
-
- posX -= spacing;
- }
- }
-
- // size
- Vector2 size = (item.Type == TimelineItemType.Action ? regularSize : offGCDSize);
-
- // position
- posX -= size.X;
- float posY = height / 2f;
- if (item.Type == TimelineItemType.OffGCD) { posY += Settings.RotationOffGCDOffset; }
-
- Vector2 position = new Vector2(pos.X + posX, pos.Y + posY - size.Y / 2f);
-
- // icon
- DrawHelper.DrawIcon(item.IconID, position, size, 1, drawList);
-
- lastValidItem = item;
- }
- }
- }
-}
diff --git a/ActionTimeline/Windows/SettingsWindow.cs b/ActionTimeline/Windows/SettingsWindow.cs
deleted file mode 100644
index 09fee04..0000000
--- a/ActionTimeline/Windows/SettingsWindow.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using ActionTimeline.Helpers;
-using Dalamud.Interface;
-using Dalamud.Interface.Windowing;
-using ImGuiNET;
-using System.Numerics;
-
-namespace ActionTimeline.Windows
-{
- public class SettingsWindow : Window
- {
- private float _scale => ImGuiHelpers.GlobalScale;
- private Settings Settings => Plugin.Settings;
-
- public SettingsWindow(string name) : base(name)
- {
- Flags = ImGuiWindowFlags.NoScrollbar
- | ImGuiWindowFlags.NoCollapse
- | ImGuiWindowFlags.NoResize
- | ImGuiWindowFlags.NoScrollWithMouse;
-
- Size = new Vector2(180, 84);
- }
-
- public override void Draw()
- {
- if (ImGui.Button("Configure Timeline Window"))
- {
- Plugin.ShowTimelineSettingsWindow();
- }
-
- if (ImGui.Button("Configure Rotation Window"))
- {
- Plugin.ShowRotationSettingsWindow();
- }
- }
- }
-}
diff --git a/ActionTimeline/Windows/TimelineSettingsWindow.cs b/ActionTimeline/Windows/TimelineSettingsWindow.cs
deleted file mode 100644
index 81ed4bd..0000000
--- a/ActionTimeline/Windows/TimelineSettingsWindow.cs
+++ /dev/null
@@ -1,167 +0,0 @@
-using ActionTimeline.Helpers;
-using Dalamud.Interface;
-using Dalamud.Interface.Windowing;
-using ImGuiNET;
-using System.Numerics;
-
-namespace ActionTimeline.Windows
-{
- public class TimelineSettingsWindow : Window
- {
- private float _scale => ImGuiHelpers.GlobalScale;
- private Settings Settings => Plugin.Settings;
-
- public TimelineSettingsWindow(string name) : base(name)
- {
- Flags = ImGuiWindowFlags.NoScrollbar
- | ImGuiWindowFlags.NoCollapse
- | ImGuiWindowFlags.NoResize
- | ImGuiWindowFlags.NoScrollWithMouse;
-
- Size = new Vector2(300, 350);
- }
-
- public override void Draw()
- {
- if (!ImGui.BeginTabBar("##Timeline_Settings_TabBar"))
- {
- return;
- }
-
- ImGui.PushItemWidth(80 * _scale);
-
- // general
- if (ImGui.BeginTabItem("General##Timeline_General"))
- {
- DrawGeneralTab();
- ImGui.EndTabItem();
- }
-
- // icons
- if (ImGui.BeginTabItem("Icons##Timeline_Icons"))
- {
- DrawIconsTab();
- ImGui.EndTabItem();
- }
-
- // casts
- if (ImGui.BeginTabItem("Casts##Timeline_Casts"))
- {
- DrawCastsTab();
- ImGui.EndTabItem();
- }
-
- // grid
- if (ImGui.BeginTabItem("Grid##Timeline_Grid"))
- {
- DrawGridTab();
- ImGui.EndTabItem();
- }
-
- // gcd clipping
- if (ImGui.BeginTabItem("GCD Clipping##Timeline_GCD"))
- {
- DrawGCDClippingTab();
- ImGui.EndTabItem();
- }
-
- ImGui.EndTabBar();
- }
-
- public void DrawGeneralTab()
- {
- ImGui.Checkbox("Enabled", ref Settings.ShowTimeline);
-
- if (!Settings.ShowTimeline) { return; }
-
- ImGui.DragInt("Time (seconds)", ref Settings.TimelineTime, 0.1f, 1, 30);
- DrawHelper.SetTooltip("This is how far in the past the timeline will go.");
-
- ImGui.NewLine();
- ImGui.Checkbox("Locked", ref Settings.TimelineLocked);
- ImGui.ColorEdit4("Locked Color", ref Settings.TimelineLockedBackgroundColor, ImGuiColorEditFlags.NoInputs);
- ImGui.ColorEdit4("Unlocked Color", ref Settings.TimelineUnlockedBackgroundColor, ImGuiColorEditFlags.NoInputs);
-
- ImGui.NewLine();
- ImGui.DragInt("Out of Combat Clear Time (seconds)", ref Settings.OutOfCombatClearTime, 0.1f, 1, 30);
- DrawHelper.SetTooltip("The timeline will be cleared after being out of combat for this many seconds.");
-
- ImGui.Checkbox("Show Only In Duty", ref Settings.ShowTimelineOnlyInDuty);
- ImGui.Checkbox("Show Only In Combat", ref Settings.ShowTimelineOnlyInCombat);
- }
-
- public void DrawIconsTab()
- {
- ImGui.DragInt("Icon Size", ref Settings.TimelineIconSize);
-
- ImGui.NewLine();
- ImGui.DragInt("Off GCD Icon Size", ref Settings.TimelineOffGCDIconSize);
- ImGui.DragInt("Iff GCD Vertical Offset", ref Settings.TimelineOffGCDOffset);
-
- ImGui.NewLine();
- ImGui.Checkbox("Show Auto Attacks", ref Settings.TimelineShowAutoAttacks);
- ImGui.DragInt("Auto Attack Icon Size", ref Settings.TimelineAutoAttackSize);
- ImGui.DragInt("Auto Attack Vertical Offset", ref Settings.TimelineAutoAttackOffset);
- }
-
- public void DrawCastsTab()
- {
- ImGui.ColorEdit4("Cast In Progress Color", ref Settings.CastInProgressColor, ImGuiColorEditFlags.NoInputs);
- ImGui.ColorEdit4("Cast Finished Color", ref Settings.CastFinishedColor, ImGuiColorEditFlags.NoInputs);
- ImGui.ColorEdit4("Cast Canceled Color", ref Settings.CastCanceledColor, ImGuiColorEditFlags.NoInputs);
- }
-
- public void DrawGridTab()
- {
- ImGui.Checkbox("Enabled", ref Settings.ShowGrid);
-
- if (!Settings.ShowGrid) { return; }
-
- ImGui.Checkbox("Show Center Line", ref Settings.ShowGridCenterLine);
- ImGui.DragInt("Line Width", ref Settings.GridLineWidth, 0.5f, 1, 5);
- ImGui.ColorEdit4("Line Color", ref Settings.GridLineColor, ImGuiColorEditFlags.NoInputs);
-
- ImGui.NewLine();
- ImGui.Checkbox("Divide By Seconds", ref Settings.GridDivideBySeconds);
-
- if (!Settings.GridDivideBySeconds) { return; }
-
- ImGui.Checkbox("Show Text", ref Settings.GridShowSecondsText);
-
- ImGui.NewLine();
- ImGui.Checkbox("Sub-Divide By Seconds", ref Settings.GridSubdivideSeconds);
-
- if (!Settings.GridSubdivideSeconds) { return; }
-
- ImGui.DragInt("Sub-Division Count", ref Settings.GridSubdivisionCount, 0.5f, 2, 8);
- ImGui.DragInt("Sub-Division Line Width", ref Settings.GridSubdivisionLineWidth, 0.5f, 1, 5);
- ImGui.ColorEdit4("Sub-Division Line Color", ref Settings.GridSubdivisionLineColor, ImGuiColorEditFlags.NoInputs);
- }
-
- public void DrawGCDClippingTab()
- {
- ImGui.Checkbox("Enabled", ref Settings.ShowGCDClipping);
-
- if (!Settings.ShowGCDClipping) { return; }
-
- int clippingThreshold = (int)(Settings.GCDClippingThreshold * 1000f);
- if (ImGui.DragInt("Threshold (ms)", ref clippingThreshold, 0.1f, 0, 1000))
- {
- Settings.GCDClippingThreshold = (float)clippingThreshold / 1000f;
- }
- DrawHelper.SetTooltip("This can be used filter out \"false positives\" due to latency or other factors. Any GCD clipping detected that is shorter than this value will be ignored.\nIt is strongly recommended that you test out different values and find out what works best for your setup.");
-
- int castClippingThresgold = (int)(Settings.GCDClippingCastsThreshold * 1000f);
- if (ImGui.DragInt("Casts Threshold (ms)", ref castClippingThresgold, 0.1f, 0, 1000))
- {
- Settings.GCDClippingCastsThreshold = (float)castClippingThresgold / 1000f;
- }
- DrawHelper.SetTooltip("This can be used filter out \"false positives\" after a cast, specially for casts that are longer than the GCD. Any GCD clipping detected after a cast that is shorter than this value will be ignored.\nIt is strongly recommended that you test out different values and find out what works best for your setup.");
-
- ImGui.DragInt("Max Time (seconds)", ref Settings.GCDClippingMaxTime, 0.1f, 3, 60);
- DrawHelper.SetTooltip("Any GCD clip longer than this will be capped");
-
- ImGui.ColorEdit4("Color", ref Settings.GCDClippingColor, ImGuiColorEditFlags.NoInputs);
- }
- }
-}
diff --git a/ActionTimeline/Windows/TimelineWindow.cs b/ActionTimeline/Windows/TimelineWindow.cs
deleted file mode 100644
index 0813524..0000000
--- a/ActionTimeline/Windows/TimelineWindow.cs
+++ /dev/null
@@ -1,217 +0,0 @@
-using ActionTimeline.Helpers;
-using Dalamud.Interface;
-using Dalamud.Interface.Windowing;
-using FFXIVClientStructs.FFXIV.Client.Game;
-using ImGuiNET;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-
-namespace ActionTimeline.Windows
-{
- internal class TimelineWindow : Window
- {
- private float _scale => ImGuiHelpers.GlobalScale;
- private Settings Settings => Plugin.Settings;
-
- private ImGuiWindowFlags _baseFlags = ImGuiWindowFlags.NoScrollbar
- | ImGuiWindowFlags.NoCollapse
- | ImGuiWindowFlags.NoTitleBar
- | ImGuiWindowFlags.NoNav
- | ImGuiWindowFlags.NoScrollWithMouse;
-
- public TimelineWindow(string name) : base(name)
- {
- Flags = _baseFlags;
-
- Size = new Vector2(560, 90);
- SizeCondition = ImGuiCond.FirstUseEver;
-
- Position = new Vector2(200, 200);
- PositionCondition = ImGuiCond.FirstUseEver;
- }
-
- public override void PreDraw()
- {
- Vector4 bgColor = Settings.TimelineLocked ? Settings.TimelineLockedBackgroundColor : Settings.TimelineUnlockedBackgroundColor;
- ImGui.PushStyleColor(ImGuiCol.WindowBg, bgColor);
-
- Flags = _baseFlags;
-
- if (Settings.TimelineLocked)
- {
- Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs;
- }
-
- ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
- ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0);
- }
-
- public override void PostDraw()
- {
- ImGui.PopStyleColor();
- ImGui.PopStyleVar(2);
- }
-
- public override void Draw()
- {
- if (ImGui.IsWindowHovered())
- {
- if (ImGui.IsMouseClicked(ImGuiMouseButton.Right))
- {
- Plugin.ShowTimelineSettingsWindow();
- }
- }
-
- DrawGrid();
-
- IReadOnlyCollection? list = TimelineManager.Instance?.Items;
- if (list == null) { return; }
-
- TimelineItem? lastGCD = list.LastOrDefault(o => o.Type == TimelineItemType.Action);
-
- ImDrawListPtr drawList = ImGui.GetWindowDrawList();
- Vector2 pos = ImGui.GetWindowPos();
- float width = ImGui.GetWindowWidth();
- float height = ImGui.GetWindowHeight();
- double now = ImGui.GetTime();
- int maxTime = Settings.TimelineTime;
-
- Vector2 regularSize = new Vector2(Settings.TimelineIconSize);
- Vector2 offGCDSize = new Vector2(Settings.TimelineOffGCDIconSize);
- Vector2 autoAttackSize = new Vector2(Settings.TimelineAutoAttackSize);
-
- uint gcdClippingColor = ImGui.ColorConvertFloat4ToU32(Settings.GCDClippingColor);
- uint castInProgressColor = ImGui.ColorConvertFloat4ToU32(Settings.CastInProgressColor);
- uint castFinishedColor = ImGui.ColorConvertFloat4ToU32(Settings.CastFinishedColor);
- uint castCanceledColor = ImGui.ColorConvertFloat4ToU32(Settings.CastCanceledColor);
-
- for (int i = 0; i < list.Count; i++)
- {
- TimelineItem item = list.ElementAt(i);
- if (!Settings.TimelineShowAutoAttacks && item.Type == TimelineItemType.AutoAttack) { continue; }
-
- // position
- float posX = GetPositionX(Math.Abs(now - item.Time), maxTime, width);
-
- float posY = height / 2f;
- if (item.Type == TimelineItemType.OffGCD) { posY += Settings.TimelineOffGCDOffset; }
- else if (item.Type == TimelineItemType.AutoAttack) { posY += Settings.TimelineAutoAttackOffset; }
-
- // size
- Vector2 size = regularSize;
- if (item.Type == TimelineItemType.OffGCD) { size = offGCDSize; }
- else if (item.Type == TimelineItemType.AutoAttack) { size = autoAttackSize; }
-
- Vector2 position = new Vector2(pos.X + posX - size.X / 2f, pos.Y + posY - size.Y / 2f);
-
- // gcd
- if (Settings.ShowGCDClipping && item.GCDClipData.HasValue && item.GCDClipData.Value.ShouldDraw)
- {
- float gcdClipStartPosX = Math.Max(0, GetPositionX(Math.Abs(now - item.GCDClipData.Value.StartTime), maxTime, width));
- Vector2 gcdClipStartPos = new Vector2(pos.X + gcdClipStartPosX, pos.Y);
-
- float gcdClipEndPosX = item.GCDClipData.Value.EndTime.HasValue ? GetPositionX(Math.Abs(now - item.GCDClipData.Value.EndTime.Value), maxTime, width) : width;
- Vector2 gcdClipEndPos = new Vector2(pos.X + gcdClipEndPosX, pos.Y + height);
-
- if (gcdClipEndPosX > 0)
- {
- drawList.AddRectFilled(gcdClipStartPos, gcdClipEndPos, gcdClippingColor);
- }
- }
-
- // cast bar
- if (item.Type == TimelineItemType.CastStart)
- {
- float endX = width;
- uint color = castInProgressColor;
-
- if (i < list.Count - 1)
- {
- TimelineItem nextItem = list.ElementAt(i + 1);
- endX = GetPositionX(Math.Abs(now - nextItem.Time), maxTime, width);
-
- if (nextItem.Type == TimelineItemType.CastCancel || nextItem.ActionID != item.ActionID)
- {
- color = castCanceledColor;
- }
- else if (nextItem.Type == TimelineItemType.Action)
- {
- color = castFinishedColor;
- }
- }
-
- Vector2 startPosition = new Vector2(position.X + size.X / 2, position.Y);
- Vector2 endPosition = new Vector2(pos.X + endX + 5, position.Y + size.Y);
- if (endPosition.X > 0)
- {
- drawList.AddRectFilled(startPosition, endPosition, color);
- }
- }
-
- if (position.X >= -size.X && item.Type != TimelineItemType.CastCancel)
- {
- DrawHelper.DrawIcon(item.IconID, position, size, 1, drawList);
- }
- }
- }
-
- private unsafe float GetGCDTime(uint actionId)
- {
- ActionManager * actionManager = ActionManager.Instance();
- uint adjustedId = actionManager->GetAdjustedActionId(actionId);
- return actionManager->GetRecastTime(ActionType.Spell, adjustedId);
- }
-
- private float GetPositionX(double timeDiff, int maxTime, float width)
- {
- return width - ((float)timeDiff * width / maxTime);
- }
-
- private void DrawGrid()
- {
- if (!Settings.ShowGrid) { return; }
-
- ImDrawListPtr drawList = ImGui.GetWindowDrawList();
- Vector2 pos = ImGui.GetWindowPos();
- float width = ImGui.GetWindowWidth();
- float height = ImGui.GetWindowHeight();
-
- double now = ImGui.GetTime();
- int maxTime = Settings.TimelineTime;
-
- uint lineColor = ImGui.ColorConvertFloat4ToU32(Settings.GridLineColor);
- uint subdivisionLineColor = ImGui.ColorConvertFloat4ToU32(Settings.GridSubdivisionLineColor);
-
- if (Settings.ShowGridCenterLine)
- {
- drawList.AddLine(new Vector2(pos.X, pos.Y + height / 2f), new Vector2(pos.X + width, pos.Y + height / 2f), lineColor, Settings.GridLineWidth);
- }
-
- if (!Settings.GridDivideBySeconds) { return; }
-
- for (int i = 0; i < maxTime; i++)
- {
- float step = width / maxTime;
- float x = step * i;
-
- if (Settings.GridSubdivideSeconds && Settings.GridSubdivisionCount > 1)
- {
- float subStep = step * 1f / (float)Settings.GridSubdivisionCount;
- for (int j = 1; j < Settings.GridSubdivisionCount; j++)
- {
- drawList.AddLine(new Vector2(pos.X + x + subStep * j, pos.Y), new Vector2(pos.X + x + subStep * j, pos.Y + height), subdivisionLineColor, Settings.GridSubdivisionLineWidth);
- }
- }
-
- drawList.AddLine(new Vector2(pos.X + x, pos.Y), new Vector2(pos.X + x, pos.Y + height), lineColor, Settings.GridLineWidth);
-
- if (Settings.GridShowSecondsText)
- {
- drawList.AddText(new Vector2(pos.X + x + 2, pos.Y), lineColor, $"-{maxTime - i}s");
- }
- }
- }
- }
-}
diff --git a/ActionTimeline/changelog.md b/ActionTimeline/changelog.md
deleted file mode 100644
index 1cad808..0000000
--- a/ActionTimeline/changelog.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# 1.2.0.0
-- Added support for Patch 6.4.
-
-# 1.1.0.0
-- Added support for Patch 6.3 and Dalamud Api8.
-
-# 1.0.0.0
-- Moving plugin out of testing.
-- Fixed Red Mage's Resolution not behaving correctly in the timeline with GCD clipping enabled.
-
-# 0.2.0.0
-- Fixed windows not being click-through when locked.
-- Fixed Monk's Anatman showing as an oGCD and not triggering the GCD in the timeline.
-- Fixed Ninja's Mudras and Ninjutsus showing as an oGCD and not triggering the GCD in the timeline.
-
-# 0.1.0.0
-- First release.
\ No newline at end of file
diff --git a/ActionTimeline/packages.lock.json b/ActionTimeline/packages.lock.json
deleted file mode 100644
index ce366a3..0000000
--- a/ActionTimeline/packages.lock.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "version": 1,
- "dependencies": {
- "net7.0": {
- "DalamudPackager": {
- "type": "Direct",
- "requested": "[2.1.10, )",
- "resolved": "2.1.10",
- "contentHash": "S6NrvvOnLgT4GDdgwuKVJjbFo+8ZEj+JsEYk9ojjOR/MMfv1dIFpT8aRJQfI24rtDcw1uF+GnSSMN4WW1yt7fw=="
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ActionTimelineEx.sln b/ActionTimelineEx.sln
new file mode 100644
index 0000000..176fa99
--- /dev/null
+++ b/ActionTimelineEx.sln
@@ -0,0 +1,46 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.6.33815.320
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{226FB92B-E6FD-4D1A-9D6F-C4292450B706}"
+ ProjectSection(SolutionItems) = preProject
+ .gitignore = .gitignore
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ECommons", "ECommons\ECommons\ECommons.csproj", "{47AFA876-BA35-40D8-A324-7B930EA66815}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActionTimelineEx", "ActionTimelineEx\ActionTimelineEx.csproj", "{875F9E4E-B981-46F7-93C2-68ABACCEA403}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {47AFA876-BA35-40D8-A324-7B930EA66815}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {47AFA876-BA35-40D8-A324-7B930EA66815}.Debug|Any CPU.Build.0 = Debug|x64
+ {47AFA876-BA35-40D8-A324-7B930EA66815}.Debug|x64.ActiveCfg = Debug|x64
+ {47AFA876-BA35-40D8-A324-7B930EA66815}.Debug|x64.Build.0 = Debug|x64
+ {47AFA876-BA35-40D8-A324-7B930EA66815}.Release|Any CPU.ActiveCfg = Release|x64
+ {47AFA876-BA35-40D8-A324-7B930EA66815}.Release|Any CPU.Build.0 = Release|x64
+ {47AFA876-BA35-40D8-A324-7B930EA66815}.Release|x64.ActiveCfg = Release|x64
+ {47AFA876-BA35-40D8-A324-7B930EA66815}.Release|x64.Build.0 = Release|x64
+ {875F9E4E-B981-46F7-93C2-68ABACCEA403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {875F9E4E-B981-46F7-93C2-68ABACCEA403}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {875F9E4E-B981-46F7-93C2-68ABACCEA403}.Debug|x64.ActiveCfg = Debug|x64
+ {875F9E4E-B981-46F7-93C2-68ABACCEA403}.Debug|x64.Build.0 = Debug|x64
+ {875F9E4E-B981-46F7-93C2-68ABACCEA403}.Release|Any CPU.ActiveCfg = Release|x64
+ {875F9E4E-B981-46F7-93C2-68ABACCEA403}.Release|Any CPU.Build.0 = Release|x64
+ {875F9E4E-B981-46F7-93C2-68ABACCEA403}.Release|x64.ActiveCfg = Release|x64
+ {875F9E4E-B981-46F7-93C2-68ABACCEA403}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {8890BB4A-4C58-48B5-9DE4-C275BFE8FDCC}
+ EndGlobalSection
+EndGlobal
diff --git a/ActionTimelineEx/ActionTimelineEx.csproj b/ActionTimelineEx/ActionTimelineEx.csproj
new file mode 100644
index 0000000..f7c93ac
--- /dev/null
+++ b/ActionTimelineEx/ActionTimelineEx.csproj
@@ -0,0 +1,48 @@
+
+
+ net7.0-windows
+ enable
+ ArchiTed
+ x64
+ AnyCPU
+ 1.0.0
+ enable
+ $(AppData)\XIVLauncher\addon\Hooks\dev\
+ true
+ true
+
+
+
+
+
+
+
+ $(DalamudLibPath)Dalamud.dll
+ False
+
+
+ $(DalamudLibPath)ImGui.NET.dll
+ False
+
+
+ $(DalamudLibPath)ImGuiScene.dll
+ False
+
+
+ $(DalamudLibPath)Lumina.dll
+ False
+
+
+ $(DalamudLibPath)Lumina.Excel.dll
+ False
+
+
+ $(DalamudLibPath)FFXIVClientStructs.dll
+ False
+
+
+ $(DalamudLibPath)Newtonsoft.Json.dll
+ False
+
+
+
diff --git a/ActionTimelineEx/ActionTimelineEx.yaml b/ActionTimelineEx/ActionTimelineEx.yaml
new file mode 100644
index 0000000..ffc3179
--- /dev/null
+++ b/ActionTimelineEx/ActionTimelineEx.yaml
@@ -0,0 +1,18 @@
+
+name: ActionTimelineEx
+author: Tischel, ArchiTed
+description: |-
+ Configurable timeline display of all the actions you use.
+punchline: Show your actions in real-time.
+repo_url: https://github.com/ArchiDog1998/ActionTimelineEx
+icon_url: https://xivapi.com/i/000000/000073_hr1.png
+dalamud_api_level: 8
+tags:
+ - UI
+category_tags:
+ - UI
+image_urls:
+ - https://raw.githubusercontent.com/ArchiDog1998/ActionTimelineEx/main/Images/example.gif
+download_link_install: https://github.com/ArchiDog1998/ActionTimelineEx/releases/latest/download/latest.zip
+download_link_update: https://github.com/ArchiDog1998/ActionTimelineEx/releases/latest/download/latest.zip
+download_link_testing: https://github.com/ArchiDog1998/ActionTimelineEx/releases/latest/download/latest.zip
\ No newline at end of file
diff --git a/ActionTimelineEx/Configurations/DrawingSettings.cs b/ActionTimelineEx/Configurations/DrawingSettings.cs
new file mode 100644
index 0000000..72e0c3b
--- /dev/null
+++ b/ActionTimelineEx/Configurations/DrawingSettings.cs
@@ -0,0 +1,73 @@
+using Dalamud.Interface.Colors;
+using System.Numerics;
+
+namespace ActionTimelineEx.Configurations;
+
+public class DrawingSettings
+{
+ public bool Enable = true;
+ public bool IsRotation = false;
+
+ public bool Locked = false;
+ public Vector4 LockedBackgroundColor = new Vector4(0f, 0f, 0f, 0.5f);
+ public Vector4 UnlockedBackgroundColor = new Vector4(0f, 0f, 0f, 0.75f);
+
+ public float SizePerSecond = 60;
+ public float CenterOffset = 0;
+
+ public int TimeOffsetSetting = 2;
+ public int TimeOffset => IsRotation ? 0 : TimeOffsetSetting;
+ public int GCDIconSize = 40;
+
+ public bool ShowOGCD = true;
+ public int OGCDIconSize = 30;
+ public float OGCDOffset = 0.1f;
+
+ public bool ShowAutoAttack = true;
+ public int AutoAttackIconSize = 15;
+ public float AutoAttackOffset = 0.1f;
+
+ public bool ShowStatus = true;
+ public int StatusIconSize = 15;
+ public float StatusIconAlpha = 0.5f;
+ public Vector4 StatusGainColor = ImGuiColors.HealerGreen;
+ public Vector4 StatusLoseColor = ImGuiColors.DalamudRed;
+
+ public bool ShowDamageType = true;
+ public Vector4 CriticalColor = ImGuiColors.DalamudOrange;
+ public Vector4 DirectColor = ImGuiColors.DalamudYellow;
+ public Vector4 CriticalDirectColor = ImGuiColors.DPSRed;
+
+ public Vector4 BackgroundColor = new Vector4(0.5f, 0.5f, 0.5f, 0.5f);
+ public Vector4 GCDBorderColor = new Vector4(0.9f, 0.9f, 0.9f, 1f);
+ public float GCDThickness = 1.5f;
+
+ public Vector4 CastInProgressColor = new Vector4(0.2f, 0.8f, 0.2f, 1f);
+ public Vector4 CastFinishedColor = new Vector4(0.5f, 0.5f, 0.5f, 1f);
+ public Vector4 CastCanceledColor = new Vector4(0.8f, 0.2f, 0.2f, 1f);
+
+ public bool ShowAnimationLock = true;
+ public Vector4 AnimationLockColor = new Vector4(0.8f, 0.7f, 0.6f, 1f);
+
+ public bool ShowStatusLine = true;
+ public float StatusLineSize = 24;
+
+ public bool ShowGrid = true;
+ public bool ShowGridCenterLine = false;
+ public bool GridDivideBySeconds = true;
+ public bool GridShowSecondsText = true;
+ public bool GridSubdivideSeconds = true;
+ public int GridSubdivisionCount = 2;
+ public float GridLineWidth = 1;
+ public float GridStartLineWidth = 3;
+ public float GridSubdivisionLineWidth = 1;
+ public Vector4 GridLineColor = new Vector4(0.3f, 0.3f, 0.3f, 1f);
+ public Vector4 GridStartLineColor = new Vector4(0.3f, 0.5f, 0.2f, 1f);
+ public Vector4 GridSubdivisionLineColor = new Vector4(0.3f, 0.3f, 0.3f, 0.2f);
+
+ public bool ShowGCDClippingSetting = true;
+ public bool ShowGCDClipping => IsRotation ? false : ShowGCDClippingSetting;
+ public float GCDClippingThreshold = 0.15f;
+ public int GCDClippingMaxTime = 5;
+ public Vector4 GCDClippingColor = new Vector4(1f, 0.2f, 0.2f, 0.3f);
+}
diff --git a/ActionTimelineEx/Configurations/Settings.cs b/ActionTimelineEx/Configurations/Settings.cs
new file mode 100644
index 0000000..91e7751
--- /dev/null
+++ b/ActionTimelineEx/Configurations/Settings.cs
@@ -0,0 +1,21 @@
+using ActionTimelineEx.Configurations;
+using Dalamud.Configuration;
+using ECommons.DalamudServices;
+
+namespace ActionTimeline;
+
+[Serializable]
+public class Settings : IPluginConfiguration
+{
+ public bool ShowTimelineOnlyInDuty = false;
+ public bool ShowTimelineOnlyInCombat = false;
+ public float StatusCheckDelay = 0.1f;
+ public DrawingSettings TimelineSetting = new DrawingSettings();
+
+ public int Version { get; set; } = 6;
+
+ public void Save()
+ {
+ Svc.PluginInterface.SavePluginConfig(this);
+ }
+}
diff --git a/ActionTimelineEx/Helpers/DrawHelper.cs b/ActionTimelineEx/Helpers/DrawHelper.cs
new file mode 100644
index 0000000..4761ec5
--- /dev/null
+++ b/ActionTimelineEx/Helpers/DrawHelper.cs
@@ -0,0 +1,131 @@
+using ActionTimelineEx.Timeline;
+using Dalamud.Interface.Colors;
+using ECommons.DalamudServices;
+using ECommons.ImGuiMethods;
+using ImGuiNET;
+using ImGuiScene;
+using Lumina.Data.Files;
+using System.Numerics;
+
+namespace ActionTimeline.Helpers;
+
+internal static class DrawHelper
+{
+ private static readonly Vector2 _uv1 = new Vector2(96 * 5 / 852f, 0),
+ _uv2 = new Vector2((96 * 5 + 144) / 852f, 0.5f);
+
+ private static TextureWrap? _roundTex;
+ public static void Init()
+ {
+ var tex = Svc.Data.GetFile("ui/uld/icona_frame_hr1.tex");
+ if (tex == null) return;
+ byte[] imageData = tex.ImageData;
+ byte[] array = new byte[imageData.Length];
+
+ for (int i = 0; i < imageData.Length; i += 4)
+ {
+ array[i] = array[i + 1] = array[i + 2] = byte.MaxValue;
+ array[i + 3] = imageData[i + 3];
+ }
+
+ _roundTex = Svc.PluginInterface.UiBuilder.LoadImageRaw(array, tex!.Header.Width, tex!.Header.Height, 4);
+ }
+
+ public static void DrawDamage(this ImDrawListPtr drawList, Vector2 position, float size, uint lightCol)
+ {
+ if(_roundTex == null) return;
+
+ var pixPerUnit = size / 82;
+
+ var outPos = position - new Vector2(pixPerUnit * 31, pixPerUnit * 31);
+ drawList.AddImage(_roundTex.ImGuiHandle, outPos, outPos + new Vector2(pixPerUnit * 144, pixPerUnit * 154),
+ _uv1, _uv2, lightCol);
+ }
+
+ public static void DrawActionIcon(this ImDrawListPtr drawList, uint iconId, Vector2 position, float size)
+ {
+ TextureWrap? texture = GetTextureFromIconId(iconId);
+ if (texture == null) return;
+
+ var pixPerUnit = size / 82;
+
+ drawList.AddImage(texture.ImGuiHandle, position, position + new Vector2(size));
+
+ //Cover.
+ if (ThreadLoadImageHandler.TryGetTextureWrap("ui/uld/icona_frame_hr1.tex", out var frameText))
+ {
+ var coverPos = position - new Vector2(pixPerUnit * 3, pixPerUnit * 4);
+ drawList.AddImage(frameText.ImGuiHandle, coverPos, coverPos + new Vector2(pixPerUnit * 88, pixPerUnit * 96),
+ new Vector2(4f / frameText.Width, 0f / frameText.Height), new Vector2(92f / frameText.Width, 96f / frameText.Height));
+ }
+ }
+
+ public static Vector4 ChangeAlpha(this Vector4 color, float alpha)
+ {
+ color.Z = alpha;
+ return color;
+ }
+
+ public static TextureWrap? GetTextureFromIconId(uint iconId, bool highQuality = false)
+ => ThreadLoadImageHandler.TryGetIconTextureWrap(iconId, highQuality, out var texture) ? texture
+ : ThreadLoadImageHandler.TryGetIconTextureWrap(0, highQuality, out texture) ? texture : null;
+
+ private static Dictionary textureColorCache = new ();
+ private static Queue calculating = new ();
+ public static uint GetTextureAverageColor(uint iconId)
+ {
+ if (textureColorCache.TryGetValue(iconId, out var color)) return color;
+
+ if (!calculating.Contains(iconId)) calculating.Enqueue(iconId);
+
+ CalculateColor();
+ return uint.MaxValue;
+ }
+
+ private static bool _run;
+ private static void CalculateColor()
+ {
+ if (_run) return;
+ _run = true;
+
+ Task.Run(() =>
+ {
+ while(calculating.TryDequeue(out var icon))
+ {
+ var tex = Svc.Data.GetIcon(icon, false);
+
+
+ Svc.Data.GetImGuiTexture(tex);
+
+ if(tex == null)
+ {
+ textureColorCache[icon] = uint.MaxValue;
+ continue;
+ }
+
+ byte[] imageData = tex.ImageData;
+ float whole = 0, r = 0, g = 0, b = 0;
+ for (int i = 0; i < imageData.Length; i += 4)
+ {
+ var alpha = imageData[i + 3] / (float)byte.MaxValue;
+ b += imageData[i] / (float)byte.MaxValue * alpha;
+ g += imageData[i + 1] / (float)byte.MaxValue * alpha;
+ r += imageData[i + 2] / (float)byte.MaxValue * alpha;
+
+ whole += alpha;
+ }
+
+ textureColorCache[icon] = ImGui.ColorConvertFloat4ToU32(new Vector4(r / whole, g / whole, b / whole, 1));
+ }
+ _run = false;
+ });
+ }
+
+ public static void SetTooltip(string message)
+ {
+ if (ImGui.IsItemHovered())
+ {
+ ImGui.SetTooltip(message);
+ }
+ }
+}
diff --git a/ActionTimelineEx/Plugin.cs b/ActionTimelineEx/Plugin.cs
new file mode 100644
index 0000000..6e1d010
--- /dev/null
+++ b/ActionTimelineEx/Plugin.cs
@@ -0,0 +1,170 @@
+using ActionTimeline.Helpers;
+using ActionTimeline.Timeline;
+using ActionTimeline.Windows;
+using Dalamud.Game.ClientState.Conditions;
+using Dalamud.Interface.Windowing;
+using Dalamud.Plugin;
+using ECommons;
+using ECommons.Commands;
+using ECommons.DalamudServices;
+using ECommons.GameHelpers;
+
+namespace ActionTimeline;
+
+public class Plugin : IDalamudPlugin
+{
+ public static readonly SortedList IconStack = new()
+ {
+ { 16203, 0 }, // Medicine.
+
+ { 10155, 1 }, //PLD Fight or Flight.
+
+ { 17926, 1 }, //DRK Blood Weapon.
+
+ { 13601, 1 }, //GNB No mercy.
+
+ { 12627, 1 }, //WHM Presence of Mind.
+
+ { 12809, 1 }, //SCH Chain Stratagem.
+
+ { 13245, 1 }, //AST Divination.
+ { 13259, 2 }, //AST Harmony of Spirit.
+
+ { 12532, 1 }, //MNK Brotherhood.
+ { 12528, 2 }, //MNK Brotherhood.
+
+ { 12578, 1 }, //DRG Battle Litany.
+ { 12581, 2 }, //DRG Right Eye.
+ { 10304, 3 }, //DRG Lance Charge.
+
+ { 12918, 1 }, //NIN Trick Attack.
+ { 15020, 2 }, //NIN Vulnerability Up.
+
+ { 12601, 1 }, //BRD Battle Voice.
+ { 12622, 2 }, //BRD Radiant Finale.
+ { 10354, 3 }, //BRD Radiant Finale.
+
+ { 13011, 1 }, //MCH Wildfire.
+
+ { 13714, 1 }, //DNC Devilment.
+ { 13709, 2 }, //DNC Technical Finish.
+
+ { 12653, 1 }, //BLM Ley Lines.
+
+ { 13409, 1 }, //RDM Embolden.
+
+ { 12699, 1 }, //SMN 2703.
+ };
+ public string Name => "ActionTimelineEx";
+
+ public static string Version { get; private set; } = "";
+
+ public static Settings Settings { get; private set; } = null!;
+
+ private static WindowSystem _windowSystem = null!;
+ private static SettingsWindow _settingsWindow = null!;
+
+ public Plugin(DalamudPluginInterface pluginInterface)
+ {
+ ECommonsMain.Init(pluginInterface, this);
+
+ Svc.PluginInterface.UiBuilder.Draw += Draw;
+ Svc.PluginInterface.UiBuilder.OpenConfigUi += OpenConfigUi;
+
+ TimelineManager.Initialize();
+ DrawHelper.Init();
+
+ try
+ {
+ Settings = pluginInterface.GetPluginConfig() as Settings ?? new Settings();
+ }
+ catch
+ {
+ Settings = new Settings();
+ }
+
+ CreateWindows();
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ [Cmd("/atl", "Opens the ActionTimeline configuration window.")]
+ private void PluginCommand(string command, string arguments)
+ {
+ _settingsWindow.IsOpen = !_settingsWindow.IsOpen;
+ }
+
+ private void CreateWindows()
+ {
+ _settingsWindow = new SettingsWindow();
+
+ _windowSystem = new WindowSystem("ActionTimeline_Windows");
+ _windowSystem.AddWindow(_settingsWindow);
+ _windowSystem.AddWindow(new TimelineWindow("Timeline")
+ {
+ Setting = Settings.TimelineSetting,
+ });
+ }
+
+ private void Draw()
+ {
+ if (Settings == null || !Player.Available) return;
+
+ UpdateTimeline();
+
+ _windowSystem?.Draw();
+ }
+
+ private void UpdateTimeline()
+ {
+ foreach (var window in _windowSystem.Windows)
+ {
+ if (window is not TimelineWindow tWindow) continue;
+
+ bool show = tWindow.Setting.Enable;
+ if (show)
+ {
+ if (Settings.ShowTimelineOnlyInCombat && !Svc.Condition[ConditionFlag.InCombat])
+ {
+ show = false;
+ }
+
+ if (Settings.ShowTimelineOnlyInDuty && !Svc.Condition[ConditionFlag.BoundByDuty])
+ {
+ show = false;
+ }
+ }
+
+ tWindow.IsOpen = show;
+ }
+ }
+
+ public static void OpenConfigUi()
+ {
+ _settingsWindow.IsOpen = true;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposing)
+ {
+ return;
+ }
+
+ Settings.Save();
+
+ TimelineManager.Instance?.Dispose();
+
+ _windowSystem.RemoveAllWindows();
+
+ ECommonsMain.Dispose();
+
+ Svc.PluginInterface.UiBuilder.Draw -= Draw;
+ Svc.PluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
+ Svc.PluginInterface.UiBuilder.RebuildFonts();
+ }
+}
diff --git a/ActionTimelineEx/Timeline/ActionCate.cs b/ActionTimelineEx/Timeline/ActionCate.cs
new file mode 100644
index 0000000..56f1a59
--- /dev/null
+++ b/ActionTimelineEx/Timeline/ActionCate.cs
@@ -0,0 +1,90 @@
+namespace RotationSolver.Basic.Data;
+
+///
+/// The action Category.
+///
+public enum ActionCate : uint
+{
+ None = 0,
+
+ ///
+ /// Auto Attack.
+ ///
+ AutoAttack = 1,
+
+ ///
+ /// Spell
+ ///
+ Spell = 2,
+
+ ///
+ /// Weapon Skill
+ ///
+ WeaponSkill = 3,
+
+ ///
+ /// Ability.
+ ///
+ Ability = 4,
+
+ ///
+ /// Items.
+ ///
+ Item = 5,
+
+ ///
+ /// Land Abilities.
+ ///
+ DoLAbility = 6,
+
+ ///
+ /// Hand Abilities.
+ ///
+ DoHAbility = 7,
+
+ ///
+ /// Events.
+ ///
+ Event = 8,
+
+ ///
+ /// LB
+ ///
+ LimitBreak9 = 9,
+
+ ///
+ /// System.
+ ///
+ System10 = 10,
+
+ ///
+ /// System.
+ ///
+ System11 = 11,
+
+ ///
+ /// Mount.
+ ///
+ Mount = 12,
+
+ ///
+ /// Special Actions.
+ ///
+ Special = 13,
+
+ ///
+ /// Item Manipulation
+ ///
+ ItemManipulation = 14,
+
+ ///
+ /// LB
+ ///
+ LimitBreak15 = 15,
+
+
+ ///
+ /// Artillery
+ ///
+ Artillery = 17,
+}
diff --git a/ActionTimelineEx/Timeline/DamageType.cs b/ActionTimelineEx/Timeline/DamageType.cs
new file mode 100644
index 0000000..617a03d
--- /dev/null
+++ b/ActionTimelineEx/Timeline/DamageType.cs
@@ -0,0 +1,9 @@
+namespace ActionTimelineEx.Timeline;
+
+public enum DamageType: byte
+{
+ None,
+ Critical,
+ Direct,
+ CriticalDirect,
+}
diff --git a/ActionTimelineEx/Timeline/ITimelineItem.cs b/ActionTimelineEx/Timeline/ITimelineItem.cs
new file mode 100644
index 0000000..864816f
--- /dev/null
+++ b/ActionTimelineEx/Timeline/ITimelineItem.cs
@@ -0,0 +1,6 @@
+namespace ActionTimelineEx.Timeline;
+
+public interface ITimelineItem
+{
+ DateTime EndTime { get; }
+}
diff --git a/ActionTimelineEx/Timeline/StatusLineItem.cs b/ActionTimelineEx/Timeline/StatusLineItem.cs
new file mode 100644
index 0000000..b1bd2e5
--- /dev/null
+++ b/ActionTimelineEx/Timeline/StatusLineItem.cs
@@ -0,0 +1,62 @@
+using ActionTimeline;
+using ActionTimeline.Helpers;
+using ActionTimelineEx.Configurations;
+using ImGuiNET;
+using ImGuiScene;
+using System.Numerics;
+
+namespace ActionTimelineEx.Timeline;
+
+public class StatusLineItem : ITimelineItem
+{
+ public uint Icon { get; set; }
+ public float TimeDuration { get; set; }
+ public DateTime StartTime { get; init; }
+
+ public DateTime EndTime => StartTime + TimeSpan.FromSeconds(TimeDuration);
+
+ public byte Stack { get; set; }
+
+ public void Draw(DateTime time, Vector2 windowPos, Vector2 windowSize, DrawingSettings setting)
+ {
+ var sizePerSecond = setting.SizePerSecond;
+ var rightCenter = windowPos + new Vector2(windowSize.X, windowSize.Y / 2 + setting.CenterOffset);
+ rightCenter -= Vector2.UnitX * setting.TimeOffset * sizePerSecond;
+ DrawItemWithCenter(rightCenter - Vector2.UnitX * (float)(time - StartTime).TotalSeconds * sizePerSecond,
+ windowPos, setting);
+ }
+
+ public void DrawItemWithCenter(Vector2 centerPos, Vector2 windowPos, DrawingSettings setting)
+ {
+ var GcdSize = setting.GCDIconSize;
+ var drawList = ImGui.GetWindowDrawList();
+ var xUnitPerSecond = Vector2.UnitX * setting.SizePerSecond;
+
+ var statusHeight = setting.StatusLineSize;
+ var flag = ImDrawFlags.RoundCornersAll;
+ var rounding = 2;
+
+ TextureWrap? texture = DrawHelper.GetTextureFromIconId(Icon);
+ if (texture == null) return;
+
+ var col = DrawHelper.GetTextureAverageColor(Icon);
+
+ var leftTop = centerPos + new Vector2(0, statusHeight * Stack + GcdSize / 2);
+ if(setting.ShowAutoAttack)
+ {
+ var autoAttackOffset = setting.AutoAttackOffset;
+ var autoAttackSize = setting.AutoAttackIconSize;
+ leftTop += Vector2.UnitY *( autoAttackOffset * GcdSize + autoAttackSize);
+ }
+ var shrink = new Vector2(0, statusHeight * 0.3f);
+ var rightBottom = leftTop + xUnitPerSecond * TimeDuration + Vector2.UnitY * statusHeight - shrink;
+ drawList.AddRectFilled(leftTop + shrink, rightBottom,col, rounding, flag);
+
+ if (rightBottom.X <= windowPos.X) return;
+
+ leftTop.X = Math.Max(leftTop.X, windowPos.X);
+
+ drawList.AddImage(texture.ImGuiHandle, leftTop,
+ leftTop + new Vector2(statusHeight / TimelineItem.HeightRatio, statusHeight), Vector2.Zero, Vector2.One);
+ }
+}
diff --git a/ActionTimelineEx/Timeline/TimelineItem.cs b/ActionTimelineEx/Timeline/TimelineItem.cs
new file mode 100644
index 0000000..20cf81f
--- /dev/null
+++ b/ActionTimelineEx/Timeline/TimelineItem.cs
@@ -0,0 +1,192 @@
+using ActionTimeline;
+using ActionTimeline.Helpers;
+using ActionTimelineEx.Configurations;
+using Dalamud.Interface;
+using Dalamud.Interface.Colors;
+using ImGuiNET;
+using ImGuiScene;
+using System.Numerics;
+
+namespace ActionTimelineEx.Timeline;
+
+public class TimelineItem : ITimelineItem
+{
+ public string? Name { get; set; }
+ public ushort Icon { get; set; }
+
+ public DateTime StartTime { get; init; }
+
+ public float AnimationLockTime { get; set; }
+
+ public float CastingTime { get; set; }
+
+ public float GCDTime { get; set; }
+
+ public TimelineItemType Type { get; set; }
+
+ public TimelineItemState State { get; set; }
+
+ public DamageType Damage { get; set; } = DamageType.None;
+
+ public float TimeDuration => MathF.Max(GCDTime, CastingTime + AnimationLockTime);
+
+ public DateTime EndTime => StartTime + TimeSpan.FromSeconds(TimeDuration);
+
+ public HashSet StatusGainIcon { get; } = new(4);
+ public HashSet StatusLoseIcon { get; } = new(4);
+
+ public void Draw(DateTime time, Vector2 windowPos, Vector2 windowSize, TimelineLayer icon, DrawingSettings setting)
+ {
+ var sizePerSecond = setting.SizePerSecond;
+ var rightCenter = windowPos + new Vector2(windowSize.X, windowSize.Y/ 2 + setting.CenterOffset);
+ rightCenter -= Vector2.UnitX * setting.TimeOffset * sizePerSecond;
+ DrawItemWithCenter(rightCenter - Vector2.UnitX * (float)(time - StartTime).TotalSeconds * sizePerSecond, icon, setting);
+ }
+
+ public void DrawItemWithCenter(Vector2 centerPos, TimelineLayer icon, DrawingSettings setting)
+ {
+ var GcdSize = setting.GCDIconSize;
+ var drawList = ImGui.GetWindowDrawList();
+ var xUnitPerSecond = Vector2.UnitX * setting.SizePerSecond;
+
+ switch (Type)
+ {
+ case TimelineItemType.GCD:
+ DrawItemWithCenter(drawList, centerPos, xUnitPerSecond, GcdSize, icon, setting);
+ break;
+
+ case TimelineItemType.OGCD when setting.ShowOGCD:
+ var oGcdOffset = setting.OGCDOffset;
+ var oGcdSize = setting.OGCDIconSize;
+ var oGcdCenter = new Vector2(centerPos.X, centerPos.Y - oGcdOffset * GcdSize - oGcdSize / 2);
+ DrawItemWithCenter(drawList, oGcdCenter, xUnitPerSecond, oGcdSize, icon, setting);
+ break;
+
+ case TimelineItemType.AutoAttack when setting.ShowAutoAttack:
+ var autoAttackOffset = setting.AutoAttackOffset;
+ var autoAttackSize = setting.AutoAttackIconSize;
+ var autoAttackCenter = new Vector2(centerPos.X, centerPos.Y + autoAttackOffset * GcdSize
+ + (autoAttackSize + GcdSize) / 2);
+ DrawItemWithCenter(drawList, autoAttackCenter, xUnitPerSecond, autoAttackSize, icon, setting);
+ break;
+ }
+ }
+
+ private static TextureWrap[] GetTextures(HashSet iconIds)
+ {
+ var result = new List(iconIds.Count);
+ foreach (var item in iconIds)
+ {
+ TextureWrap? texture = DrawHelper.GetTextureFromIconId(item);
+ if (texture == null) continue;
+ result.Add(texture);
+ }
+ return result.ToArray();
+ }
+
+ public const float HeightRatio = 4 / 3f;
+ private void DrawItemWithCenter(ImDrawListPtr drawList, Vector2 centerPos, Vector2 xUnitPerSecond, float iconSize, TimelineLayer icon, DrawingSettings setting)
+ {
+ switch (icon)
+ {
+ case TimelineLayer.Icon:
+ drawList.DrawActionIcon(Icon, new Vector2(centerPos.X, centerPos.Y - iconSize / 2), iconSize);
+ return;
+
+ case TimelineLayer.Status when setting.ShowStatus:
+ var statusSize = setting.StatusIconSize;
+
+ var center = new Vector2(centerPos.X + iconSize / 2, centerPos.Y - iconSize / 2 - statusSize * HeightRatio);
+ var gains = GetTextures(StatusGainIcon);
+ var lose = GetTextures(StatusLoseIcon);
+ var color = ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, setting.StatusIconAlpha));
+ var gainColor = ImGui.ColorConvertFloat4ToU32(setting.StatusGainColor);
+ var loseColor = ImGui.ColorConvertFloat4ToU32(setting.StatusLoseColor);
+
+ center -= Vector2.UnitX * statusSize * (gains.Length + lose.Length) / 2;
+ for (int i = 0; i < gains.Length; i++)
+ {
+ drawList.AddImage(gains[i].ImGuiHandle, center,
+ center + new Vector2(statusSize, statusSize * HeightRatio), Vector2.Zero, Vector2.One, color);
+
+ drawList.AddText(UiBuilder.IconFont, statusSize / 2f, center, gainColor, FontAwesomeIcon.Plus.ToIconString());
+
+ center += Vector2.UnitX * statusSize;
+ }
+ for (int i = 0; i < lose.Length; i++)
+ {
+ drawList.AddImage(lose[i].ImGuiHandle, center,
+ center + new Vector2(statusSize, statusSize * HeightRatio), Vector2.Zero, Vector2.One, color);
+
+ drawList.AddText(UiBuilder.IconFont, statusSize / 2f, center, loseColor, FontAwesomeIcon.Ban.ToIconString());
+
+ center += Vector2.UnitX * statusSize;
+ }
+
+ return;
+
+ case TimelineLayer.General:
+ //Get Info.
+ float highPos = 0.5f;
+ float lowPos = 0.8f;
+ float rounding = 2f;
+
+ var leftTop = new Vector2(centerPos.X, centerPos.Y - iconSize / 2 + highPos * iconSize);
+ var leftBottom = new Vector2(centerPos.X, centerPos.Y - iconSize / 2 + lowPos * iconSize);
+ var flag = ImDrawFlags.RoundCornersAll;
+
+ var minX = centerPos.X + iconSize / 2;
+
+ //Background
+ var GcdBackColor = ImGui.ColorConvertFloat4ToU32(setting.BackgroundColor);
+ drawList.AddRectFilled(MinX(leftTop, minX), MinX(leftBottom + xUnitPerSecond * MathF.Max(GCDTime, setting.ShowAnimationLock ? CastingTime + AnimationLockTime : CastingTime), minX), GcdBackColor, rounding, flag);
+
+ var castOffset = xUnitPerSecond * CastingTime;
+
+ //AnimationLock
+ if (setting.ShowAnimationLock)
+ {
+ var animationLockColor = ImGui.ColorConvertFloat4ToU32(setting.AnimationLockColor);
+ drawList.AddRectFilled(MinX(leftTop, minX),
+ MinX(leftBottom + castOffset + xUnitPerSecond * AnimationLockTime, minX),
+ animationLockColor, rounding, flag);
+ }
+
+ //Casting
+ var castColor = State switch
+ {
+ TimelineItemState.Canceled => ImGui.ColorConvertFloat4ToU32(setting.CastCanceledColor),
+ TimelineItemState.Casting => ImGui.ColorConvertFloat4ToU32(setting.CastInProgressColor),
+ _ => ImGui.ColorConvertFloat4ToU32(setting.CastFinishedColor)
+ };
+ drawList.AddRectFilled(MinX(leftTop, minX), MinX(leftBottom + castOffset, minX), castColor, rounding, flag);
+
+ //GCD Fore
+ var GcdForeColor = ImGui.ColorConvertFloat4ToU32(setting.GCDBorderColor);
+ drawList.AddRect(MinX(leftTop, minX),
+ MinX(leftBottom + xUnitPerSecond * GCDTime, minX),
+ GcdForeColor, rounding, flag, setting.GCDThickness);
+
+ //Damage
+ if (setting.ShowDamageType)
+ {
+ var lightCol = Damage switch
+ {
+ DamageType.Critical => ImGui.ColorConvertFloat4ToU32(setting.CriticalColor),
+ DamageType.Direct => ImGui.ColorConvertFloat4ToU32(setting.DirectColor),
+ DamageType.CriticalDirect => ImGui.ColorConvertFloat4ToU32(setting.CriticalDirectColor),
+ _ => 0u,
+ };
+ drawList.DrawDamage(new Vector2(centerPos.X, centerPos.Y - iconSize / 2), iconSize, lightCol);
+ }
+
+ return;
+ }
+ //Name
+ }
+
+ private Vector2 MinX(Vector2 pos, float minPos)
+ {
+ return new Vector2(MathF.Max(pos.X, minPos), pos.Y);
+ }
+}
diff --git a/ActionTimelineEx/Timeline/TimelineLayer.cs b/ActionTimelineEx/Timeline/TimelineLayer.cs
new file mode 100644
index 0000000..e21a4f8
--- /dev/null
+++ b/ActionTimelineEx/Timeline/TimelineLayer.cs
@@ -0,0 +1,8 @@
+namespace ActionTimelineEx.Timeline;
+
+public enum TimelineLayer : byte
+{
+ General,
+ Status,
+ Icon,
+}
diff --git a/ActionTimelineEx/Timeline/TimelineManager.cs b/ActionTimelineEx/Timeline/TimelineManager.cs
new file mode 100644
index 0000000..c6a0d50
--- /dev/null
+++ b/ActionTimelineEx/Timeline/TimelineManager.cs
@@ -0,0 +1,395 @@
+using ActionTimelineEx.Timeline;
+using Dalamud.Game.ClientState.Objects.SubKinds;
+using Dalamud.Game.ClientState.Objects.Types;
+using Dalamud.Hooking;
+using Dalamud.Logging;
+using Dalamud.Utility.Signatures;
+using ECommons.DalamudServices;
+using ECommons.GameHelpers;
+using ECommons.Hooks;
+using ECommons.Hooks.ActionEffectTypes;
+using FFXIVClientStructs.FFXIV.Client.Game;
+using RotationSolver.Basic.Data;
+using Action = Lumina.Excel.GeneratedSheets.Action;
+using Status = Lumina.Excel.GeneratedSheets.Status;
+
+namespace ActionTimeline.Timeline;
+
+public class TimelineManager
+{
+ internal const byte GCDCooldownGroup = 58;
+
+ #region singleton
+ public static void Initialize() { Instance = new TimelineManager(); }
+
+ public static TimelineManager Instance { get; private set; } = null!;
+
+ public TimelineManager()
+ {
+ try
+ {
+ SignatureHelper.Initialise(this);
+ _onActorControlHook?.Enable();
+ _onCastHook?.Enable();
+
+ ActionEffect.ActionEffectEvent += ActionFromSelfAsync;
+ }
+ catch (Exception e)
+ {
+ PluginLog.Error("Error initiating hooks: " + e.Message);
+ }
+ }
+
+ ~TimelineManager()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected void Dispose(bool disposing)
+ {
+ if (!disposing)
+ {
+ return;
+ }
+
+ _items.Clear();
+
+ ActionEffect.ActionEffectEvent -= ActionFromSelfAsync;
+
+ _onActorControlHook?.Disable();
+ _onActorControlHook?.Dispose();
+
+ _onCastHook?.Disable();
+ _onCastHook?.Dispose();
+ }
+ #endregion
+
+ private delegate void OnActorControlDelegate(uint entityId, uint id, uint unk1, uint type, uint unk2, uint unk3, uint unk4, uint unk5, ulong targetId, byte unk6);
+ [Signature("E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64", DetourName = nameof(OnActorControl))]
+ private readonly Hook? _onActorControlHook = null;
+
+ private delegate void OnCastDelegate(uint sourceId, IntPtr sourceCharacter);
+ [Signature("40 55 56 48 81 EC ?? ?? ?? ?? 48 8B EA", DetourName = nameof(OnCast))]
+ private readonly Hook? _onCastHook = null;
+
+
+ public DateTime EndTime { get; private set; } = DateTime.Now;
+ private static int kMaxItemCount = 128;
+ private readonly Queue _items = new Queue(kMaxItemCount);
+ private TimelineItem? _lastItem = null;
+ private void AddItem(TimelineItem item)
+ {
+ if (item == null) return;
+ if (_items.Count >= kMaxItemCount)
+ {
+ _items.Dequeue();
+ }
+ _items.Enqueue(item);
+ _lastItem = item;
+ UpdateEndTime(item.EndTime);
+ }
+
+ private void UpdateEndTime(DateTime endTime)
+ {
+ if(endTime > EndTime) EndTime = endTime;
+ }
+
+ public List GetItems(DateTime time, out DateTime lastEndTime)
+ {
+ return GetItems(_items, time, out lastEndTime);
+ }
+
+ private static int kMaxStatusCount = 16;
+ private readonly Queue _statusItems = new Queue(kMaxStatusCount);
+ private void AddItem(StatusLineItem item)
+ {
+ if (item == null) return;
+ if (_statusItems.Count >= kMaxStatusCount)
+ {
+ _statusItems.Dequeue();
+ }
+ _statusItems.Enqueue(item);
+ }
+
+ public List GetStatus(DateTime time, out DateTime lastEndTime)
+ {
+ return GetItems(_statusItems, time, out lastEndTime);
+ }
+
+ private List GetItems(IEnumerable items, DateTime time, out DateTime lastEndTime) where T : ITimelineItem
+ {
+ var result = new List();
+ lastEndTime = DateTime.Now;
+ foreach (var item in items)
+ {
+ if (item == null) continue;
+ if (item.EndTime > time)
+ {
+ result.Add(item);
+ }
+ else if(item is TimelineItem tItem && tItem.Type == TimelineItemType.GCD)
+ {
+ lastEndTime = item.EndTime;
+ }
+ }
+ return result;
+ }
+
+ public unsafe float GCD
+ {
+ get
+ {
+ var cdGrp = ActionManager.Instance()->GetRecastGroupDetail(GCDCooldownGroup - 1);
+ return cdGrp->Total - cdGrp->Elapsed;
+ }
+ }
+
+ private static TimelineItemType GetActionType(uint actionId, ActionType type)
+ {
+
+ switch (type)
+ {
+ case ActionType.Spell:
+ var action = Svc.Data.GetExcelSheet()?.GetRow(actionId);
+ if (action == null) break;
+
+ if (actionId == 3) return TimelineItemType.OGCD; //Sprint
+
+ var actionCate = (ActionCate)(action.ActionCategory.Value?.RowId ?? 0);
+
+ var isRealGcd = action.CooldownGroup == GCDCooldownGroup || action.AdditionalCooldownGroup == GCDCooldownGroup;
+ return actionCate == ActionCate.AutoAttack
+ ? TimelineItemType.AutoAttack
+ : !isRealGcd && actionCate == ActionCate.Ability ? TimelineItemType.OGCD
+ : TimelineItemType.GCD;
+
+ case ActionType.Item:
+ return TimelineItemType.OGCD;
+ }
+
+ return TimelineItemType.GCD;
+ }
+
+ private void CancelCasting()
+ {
+ if (_lastItem == null || _lastItem.CastingTime == 0) return;
+
+ _lastItem.State = TimelineItemState.Canceled;
+ var maxTime = (float)(DateTime.Now - _lastItem.StartTime).TotalSeconds;
+ _lastItem.GCDTime = 0;
+ _lastItem.CastingTime = MathF.Min(maxTime, _lastItem.CastingTime);
+ }
+
+ private async void ActionFromSelfAsync(ActionEffectSet set)
+ {
+ if (!Player.Available) return;
+
+#if DEBUG
+ //Svc.Chat.Print($"Id: {set.Header.ActionID}; {set.Header.ActionType}");
+#endif
+ if (set.Source.ObjectId != Player.Object.ObjectId) return;
+
+ DamageType damage = DamageType.None;
+ if(set.TargetEffects[0][0].type is ActionEffectType.Damage or ActionEffectType.Heal)
+ {
+ var flag = set.TargetEffects[0][0].param0;
+ var hasDirect = (flag & 64) == 64;
+ var hasCritical = (flag & 32) == 32;
+ damage = hasCritical ? (hasDirect ? DamageType.CriticalDirect : DamageType.Critical)
+ : hasDirect ? DamageType.Direct : DamageType.None;
+ }
+
+ List statusGain = new List(), statusLose = new List();
+ var isTargetMe = set.TargetEffects[0].TargetID == Player.Object.ObjectId;
+ set.TargetEffects[0].ForEach(x =>
+ {
+ var status = Svc.Data.GetExcelSheet()?.GetRow(x.value);
+ if (status == null) return;
+
+ var icon = status.Icon;
+
+ switch (x.type)
+ {
+ case ActionEffectType.ApplyStatusEffectTarget:
+ case ActionEffectType.ApplyStatusEffectSource when isTargetMe:
+ case ActionEffectType.GpGain:
+ statusGain.Add(icon + (uint)Math.Max(0, status.MaxStacks - 1));
+ break;
+
+ case ActionEffectType.LoseStatusEffectTarget:
+ case ActionEffectType.LoseStatusEffectSource when isTargetMe:
+ var stack = Player.Object.StatusList.FirstOrDefault(s => s.StatusId == x.value)?.StackCount ?? 0;
+ stack++;
+ statusLose.Add(icon + (uint)Math.Max(0, stack - 1));
+ break;
+ }
+ });
+
+ var type = GetActionType(set.Header.ActionID, set.Header.ActionType);
+
+ if (_lastItem != null && _lastItem.CastingTime > 0 && type == TimelineItemType.GCD
+ && _lastItem.State == TimelineItemState.Casting) // Finish the casting.
+ {
+ _lastItem.State = TimelineItemState.Finished;
+ _lastItem.AnimationLockTime = set.Header.AnimationLockTime;
+ _lastItem.Name = set.Name;
+ _lastItem.Icon = set.IconId;
+ _lastItem.Damage = damage;
+ statusGain.ForEach(i => _lastItem.StatusGainIcon.Add(i));
+ statusLose.ForEach(i => _lastItem.StatusLoseIcon.Add(i));
+ }
+ else
+ {
+ var item = new TimelineItem()
+ {
+ Name = set.Name,
+ Icon = set.IconId,
+ StartTime = DateTime.Now,
+ AnimationLockTime = type == TimelineItemType.AutoAttack ? 0 : set.Header.AnimationLockTime,
+ GCDTime = type == TimelineItemType.GCD ? GCD : 0,
+ Type = type,
+ State = TimelineItemState.Finished,
+ Damage = damage,
+ };
+
+ statusGain.ForEach(i => item.StatusGainIcon.Add(i));
+ statusLose.ForEach(i => item.StatusLoseIcon.Add(i));
+
+ AddItem(item);
+ }
+
+ var effectItem = _lastItem;
+
+ if( effectItem != null)
+ {
+ UpdateEndTime(effectItem.EndTime);
+ }
+
+ if (effectItem?.Type is TimelineItemType.AutoAttack) return;
+
+ int statusDelay = 0;
+ if(Plugin.Settings.StatusCheckDelay is > 0 and < 0.5f)
+ {
+ statusDelay = (int)(Plugin.Settings.StatusCheckDelay * 1000);
+ var previousStatus = Player.Object.StatusList
+ .Where(s => s.SourceId == Player.Object.ObjectId && (s.RemainingTime > Plugin.Settings.StatusCheckDelay || s.RemainingTime <= 0))
+ .Select(s => (s.StatusId, s.StackCount))
+ .ToArray();
+
+ await Task.Delay(statusDelay);
+
+ var nowStatus = Player.Object.StatusList
+ .Where(s => s.SourceId == Player.Object.ObjectId)
+ .Select(s => (s.StatusId, s.StackCount))
+ .ToArray();
+
+ foreach (var pre in previousStatus)
+ {
+ var status = Svc.Data.GetExcelSheet()?.GetRow(pre.StatusId);
+ if (status == null) continue;
+ var now = nowStatus.FirstOrDefault(i => i.StatusId == pre.StatusId);
+ if (now.StatusId == 0 || now.StackCount < pre.StackCount)
+ {
+ effectItem?.StatusLoseIcon.Add(status.Icon + (uint)Math.Max(0, pre.StackCount - 1));
+ }
+ }
+
+ foreach (var now in nowStatus)
+ {
+ var status = Svc.Data.GetExcelSheet()?.GetRow(now.StatusId);
+ if (status == null) continue;
+ var pre = previousStatus.FirstOrDefault(i => i.StatusId == now.StatusId);
+ if (pre.StatusId == 0)
+ {
+ effectItem?.StatusGainIcon.Add(status.Icon + (uint)Math.Max(0, now.StackCount - 1));
+ }
+ }
+ }
+
+ if (effectItem != null)
+ {
+ List list = new List(4);
+ foreach (var icon in effectItem.StatusGainIcon)
+ {
+ if (Plugin.IconStack.TryGetValue(icon, out var stack))
+ {
+ var item = new StatusLineItem()
+ {
+ Icon = icon,
+ TimeDuration = 6,
+ Stack = stack,
+ StartTime = effectItem.StartTime,
+ };
+ list.Add(item);
+ AddItem(item);
+ }
+ }
+
+ var statusList = Player.Object.StatusList.Where(s => s.SourceId == Player.Object.ObjectId);
+ if ( Svc.Objects.SearchById(set.TargetEffects[0].TargetID) is BattleChara b)
+ {
+ statusList = statusList.Union(b.StatusList.Where(s => s.SourceId == Player.Object.ObjectId));
+ }
+
+ await Task.Delay(1000 - statusDelay);
+
+ foreach (var status in statusList)
+ {
+ var icon = Svc.Data.GetExcelSheet()?.GetRow(status.StatusId)?.Icon;
+ if (icon == null) continue;
+
+ foreach (var item in list)
+ {
+ if (item.Icon == icon)
+ {
+ item.TimeDuration = (float)(DateTime.Now - effectItem.StartTime).TotalSeconds + status.RemainingTime;
+ }
+ }
+ }
+ }
+ }
+
+ private void OnActorControl(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg4, uint arg5, ulong targetId, byte a10)
+ {
+ _onActorControlHook?.Original(entityId, type, buffID, direct, actionId, sourceId, arg4, arg5, targetId, a10);
+
+ if (type != 15) { return; }
+
+ PlayerCharacter? player = Player.Object;
+ if (player == null || entityId != player.ObjectId) { return; }
+
+ CancelCasting();
+ }
+
+ private unsafe void OnCast(uint sourceId, IntPtr ptr)
+ {
+ _onCastHook?.Original(sourceId, ptr);
+
+ PlayerCharacter? player = Player.Object;
+ if (player == null || sourceId != player.ObjectId) { return; }
+
+ var actionId = *(ushort*)ptr;
+
+ var action = Svc.Data.GetExcelSheet()?.GetRow(actionId);
+
+ var icon = actionId == 4 ? (ushort)118 //Mount
+ : action?.Icon ?? 0;
+
+ AddItem(new TimelineItem()
+ {
+ Name = action?.Name ?? string.Empty,
+ Icon = icon,
+ StartTime = DateTime.Now,
+ GCDTime = GCD,
+ CastingTime = Player.Object.TotalCastTime - Player.Object.CurrentCastTime,
+ Type = TimelineItemType.GCD,
+ State = TimelineItemState.Casting,
+ });
+ }
+}
diff --git a/ActionTimelineEx/Timeline/TimelineType.cs b/ActionTimelineEx/Timeline/TimelineType.cs
new file mode 100644
index 0000000..d9c7674
--- /dev/null
+++ b/ActionTimelineEx/Timeline/TimelineType.cs
@@ -0,0 +1,14 @@
+namespace ActionTimelineEx.Timeline;
+
+public enum TimelineItemType : byte
+{
+ GCD,
+ OGCD,
+ AutoAttack,
+}
+public enum TimelineItemState : byte
+{
+ Casting,
+ Canceled,
+ Finished,
+}
diff --git a/ActionTimelineEx/Windows/SettingsWindow.cs b/ActionTimelineEx/Windows/SettingsWindow.cs
new file mode 100644
index 0000000..9ab84a7
--- /dev/null
+++ b/ActionTimelineEx/Windows/SettingsWindow.cs
@@ -0,0 +1,269 @@
+using ActionTimeline.Helpers;
+using ActionTimelineEx.Configurations;
+using Dalamud.Interface;
+using Dalamud.Interface.Windowing;
+using ImGuiNET;
+using System.Drawing;
+using System.Numerics;
+
+namespace ActionTimeline.Windows
+{
+ public class SettingsWindow : Window
+ {
+ private float _scale => ImGuiHelpers.GlobalScale;
+ private Settings Settings => Plugin.Settings;
+
+ public SettingsWindow() : base("ActionTimelineEx v" + typeof(SettingsWindow).Assembly.GetName().Version?.ToString() ?? string.Empty)
+ {
+ SizeCondition = ImGuiCond.FirstUseEver;
+ Size = new Vector2(300, 490f);
+ RespectCloseHotkey = true;
+ }
+
+ public override void OnClose()
+ {
+ Settings.Save();
+ base.OnClose();
+ }
+
+ public override void Draw()
+ {
+ if (!ImGui.BeginTabBar("ActionTimelineEx Bar")) return;
+
+ if (ImGui.BeginTabItem("General"))
+ {
+ DrawGeneralSetting();
+ ImGui.EndTabItem();
+ }
+ if (ImGui.BeginTabItem("Timeline"))
+ {
+ DrawTimelineSetting(Settings.TimelineSetting);
+ ImGui.EndTabItem();
+ }
+ ImGui.EndTabBar();
+ }
+
+ private void DrawGeneralSetting()
+ {
+ ImGui.Checkbox("Show Only In Duty", ref Settings.ShowTimelineOnlyInDuty);
+ ImGui.Checkbox("Show Only In Combat", ref Settings.ShowTimelineOnlyInCombat);
+
+ ImGui.NewLine();
+
+ ImGui.DragFloat("Status checking delay (seconds)", ref Settings.StatusCheckDelay, 0.01f, 0, 1);
+ }
+
+ #region Timeline
+
+ private void DrawTimelineSetting(DrawingSettings settings)
+ {
+ if (!ImGui.BeginTabBar("##Timeline_Settings_TabBar"))
+ {
+ return;
+ }
+
+ ImGui.PushItemWidth(80 * _scale);
+
+ // general
+ if (ImGui.BeginTabItem("General##Timeline_General"))
+ {
+ DrawGeneralTab(settings);
+ ImGui.EndTabItem();
+ }
+
+ // icons
+ if (ImGui.BeginTabItem("Icons##Timeline_Icons"))
+ {
+ DrawIconsTab(settings);
+ ImGui.EndTabItem();
+ }
+
+ // casts
+ if (ImGui.BeginTabItem("Bar##Timeline_Bar"))
+ {
+ DrawBarTab(settings);
+ ImGui.EndTabItem();
+ }
+
+ // grid
+ if (ImGui.BeginTabItem("Grid##Timeline_Grid"))
+ {
+ DrawGridTab(settings);
+ ImGui.EndTabItem();
+ }
+
+ // gcd clipping
+ if (ImGui.BeginTabItem("GCD Clipping##Timeline_GCD"))
+ {
+ DrawGCDClippingTab(settings);
+ ImGui.EndTabItem();
+ }
+
+ ImGui.EndTabBar();
+ }
+
+ private void DrawGeneralTab(DrawingSettings settings)
+ {
+ ImGui.Checkbox("Enable", ref settings.Enable);
+ ImGui.Checkbox("Is Rotation", ref settings.IsRotation);
+
+ ImGui.NewLine();
+
+ ImGui.Checkbox("Locked", ref settings.Locked);
+ ImGui.ColorEdit4("Locked Color", ref settings.LockedBackgroundColor, ImGuiColorEditFlags.NoInputs);
+ ImGui.ColorEdit4("Unlocked Color", ref settings.UnlockedBackgroundColor, ImGuiColorEditFlags.NoInputs);
+
+ ImGui.NewLine();
+
+ ImGui.DragFloat("Size per second", ref settings.SizePerSecond, 0.3f, 20, 150);
+ DrawHelper.SetTooltip("This is the width of every second drawn on the window.");
+
+ if (!settings.IsRotation)
+ {
+ ImGui.DragInt("Offset Time (seconds)", ref settings.TimeOffsetSetting, 0.1f, 0, 10);
+ DrawHelper.SetTooltip("This is the advanced time about action using");
+ }
+ ImGui.DragFloat("Drawing Center offset", ref settings.CenterOffset, 0.3f, -500, 500);
+
+
+ }
+
+ private void DrawIconsTab(DrawingSettings settings)
+ {
+ ImGui.DragInt("Icon Size", ref settings.GCDIconSize);
+
+ ImGui.NewLine();
+ ImGui.Checkbox("Show Off GCD", ref settings.ShowOGCD);
+
+ if (settings.ShowOGCD)
+ {
+ ImGui.Indent();
+ ImGui.DragInt("Off GCD Icon Size", ref settings.OGCDIconSize, 0.2f, 1, 100);
+ ImGui.DragFloat("Iff GCD Vertical Offset", ref settings.OGCDOffset, 0.1f, 0, 1);
+ ImGui.Unindent();
+ }
+
+ ImGui.NewLine();
+ ImGui.Checkbox("Show Auto Attacks", ref settings.ShowAutoAttack);
+
+ if (settings.ShowAutoAttack)
+ {
+ ImGui.Indent();
+ ImGui.DragInt("Auto Attack Icon Size", ref settings.AutoAttackIconSize, 0.2f, 1, 100);
+ ImGui.DragFloat("Auto Attack Vertical Offset", ref settings.AutoAttackOffset, 0.01f, 0, 1);
+ ImGui.Unindent();
+ }
+
+ ImGui.NewLine();
+ ImGui.Checkbox("Show Status Gain Lose", ref settings.ShowStatus);
+
+ if (settings.ShowStatus)
+ {
+ ImGui.Indent();
+ ImGui.DragInt("Status Icon Size", ref settings.StatusIconSize, 0.2f, 1, 100);
+ ImGui.DragFloat("Status Icon Alpha", ref settings.StatusIconAlpha, 0.01f, 0, 1);
+ ImGui.ColorEdit4("Status Gain Color", ref settings.StatusGainColor, ImGuiColorEditFlags.NoInputs);
+ ImGui.ColorEdit4("Status Lose Color", ref settings.StatusLoseColor, ImGuiColorEditFlags.NoInputs);
+ ImGui.Unindent();
+ }
+
+ ImGui.NewLine();
+ ImGui.Checkbox("Show Damage Type", ref settings.ShowDamageType);
+ if (settings.ShowDamageType)
+ {
+ ImGui.Indent();
+ ImGui.ColorEdit4("Critical Color", ref settings.CriticalColor, ImGuiColorEditFlags.NoInputs);
+ ImGui.ColorEdit4("Direct Color", ref settings.DirectColor, ImGuiColorEditFlags.NoInputs);
+ ImGui.ColorEdit4("Critical Direct Color", ref settings.CriticalDirectColor, ImGuiColorEditFlags.NoInputs);
+ ImGui.Unindent();
+ }
+ }
+
+ private void DrawBarTab(DrawingSettings settings)
+ {
+ ImGui.ColorEdit4("Bar Background Color", ref settings.BackgroundColor, ImGuiColorEditFlags.NoInputs);
+ ImGui.ColorEdit4("GCD Border Color", ref settings.GCDBorderColor, ImGuiColorEditFlags.NoInputs);
+ ImGui.DragFloat("GCD Border Thickness", ref settings.GCDThickness, 0.01f, 0, 10);
+
+ ImGui.NewLine();
+
+ ImGui.ColorEdit4("Cast In Progress Color", ref settings.CastInProgressColor, ImGuiColorEditFlags.NoInputs);
+ ImGui.ColorEdit4("Cast Finished Color", ref settings.CastFinishedColor, ImGuiColorEditFlags.NoInputs);
+ ImGui.ColorEdit4("Cast Canceled Color", ref settings.CastCanceledColor, ImGuiColorEditFlags.NoInputs);
+
+ ImGui.NewLine();
+
+ ImGui.Checkbox("Show Animation Lock Time", ref settings.ShowAnimationLock);
+
+ if (settings.ShowAutoAttack)
+ {
+ ImGui.Indent();
+ ImGui.ColorEdit4("Animation Lock Color", ref settings.AnimationLockColor, ImGuiColorEditFlags.NoInputs);
+ ImGui.Unindent();
+ }
+
+ ImGui.NewLine();
+
+ ImGui.Checkbox("Show Status Line", ref settings.ShowStatusLine);
+
+ if (settings.ShowAutoAttack)
+ {
+ ImGui.Indent();
+ ImGui.DragFloat("Status Line Height", ref settings.StatusLineSize, 0.2f, 1, 100);
+ ImGui.Unindent();
+ }
+ }
+
+ private void DrawGridTab(DrawingSettings settings)
+ {
+ ImGui.Checkbox("Enabled", ref settings.ShowGrid);
+
+ ImGui.DragFloat("Start Line Width", ref settings.GridStartLineWidth, 0.5f, 1, 5);
+ ImGui.ColorEdit4("Start Line Color", ref settings.GridStartLineColor, ImGuiColorEditFlags.NoInputs);
+
+
+ if (!settings.ShowGrid) { return; }
+
+ ImGui.Checkbox("Show Center Line", ref settings.ShowGridCenterLine);
+ ImGui.DragFloat("Line Width", ref settings.GridLineWidth, 0.5f, 1, 5);
+ ImGui.ColorEdit4("Line Color", ref settings.GridLineColor, ImGuiColorEditFlags.NoInputs);
+
+ ImGui.NewLine();
+ ImGui.Checkbox("Divide By Seconds", ref settings.GridDivideBySeconds);
+
+ if (!settings.GridDivideBySeconds) { return; }
+
+ ImGui.Checkbox("Show Text", ref settings.GridShowSecondsText);
+
+ ImGui.NewLine();
+ ImGui.Checkbox("Sub-Divide By Seconds", ref settings.GridSubdivideSeconds);
+
+ if (!settings.GridSubdivideSeconds) { return; }
+
+ ImGui.DragInt("Sub-Division Count", ref settings.GridSubdivisionCount, 0.5f, 2, 8);
+ ImGui.DragFloat("Sub-Division Line Width", ref settings.GridSubdivisionLineWidth, 0.5f, 1, 5);
+ ImGui.ColorEdit4("Sub-Division Line Color", ref settings.GridSubdivisionLineColor, ImGuiColorEditFlags.NoInputs);
+ }
+
+ private void DrawGCDClippingTab(DrawingSettings settings)
+ {
+ ImGui.Checkbox("Enabled", ref settings.ShowGCDClippingSetting);
+ DrawHelper.SetTooltip("This only shown when timeline is not rotation.");
+
+ if (!settings.ShowGCDClipping) return;
+
+ int clippingThreshold = (int)(settings.GCDClippingThreshold * 1000f);
+ if (ImGui.DragInt("Threshold (ms)", ref clippingThreshold, 0.1f, 0, 1000))
+ {
+ settings.GCDClippingThreshold = (float)clippingThreshold / 1000f;
+ }
+ DrawHelper.SetTooltip("This can be used filter out \"false positives\" due to latency or other factors. Any GCD clipping detected that is shorter than this value will be ignored.\nIt is strongly recommended that you test out different values and find out what works best for your setup.");
+
+ ImGui.DragInt("Max Time (seconds)", ref settings.GCDClippingMaxTime, 0.1f, 3, 60);
+ DrawHelper.SetTooltip("Any GCD clip longer than this will be capped");
+
+ ImGui.ColorEdit4("Color", ref settings.GCDClippingColor, ImGuiColorEditFlags.NoInputs);
+ }
+ #endregion
+ }
+}
diff --git a/ActionTimelineEx/Windows/TimelineWindow.cs b/ActionTimelineEx/Windows/TimelineWindow.cs
new file mode 100644
index 0000000..123368f
--- /dev/null
+++ b/ActionTimelineEx/Windows/TimelineWindow.cs
@@ -0,0 +1,178 @@
+using ActionTimeline.Timeline;
+using ActionTimelineEx.Configurations;
+using ActionTimelineEx.Timeline;
+using Dalamud.Interface.Windowing;
+using ImGuiNET;
+using System.Numerics;
+
+namespace ActionTimeline.Windows;
+
+internal class TimelineWindow : Window
+{
+ public DrawingSettings Setting { get; set; } = new DrawingSettings();
+
+ private ImGuiWindowFlags _baseFlags = ImGuiWindowFlags.NoScrollbar
+ | ImGuiWindowFlags.NoCollapse
+ | ImGuiWindowFlags.NoTitleBar
+ | ImGuiWindowFlags.NoNav
+ | ImGuiWindowFlags.NoScrollWithMouse;
+
+ public TimelineWindow(string name) : base(name)
+ {
+ Flags = _baseFlags;
+
+ Size = new Vector2(560, 100);
+ SizeCondition = ImGuiCond.FirstUseEver;
+
+ Position = new Vector2(200, 200);
+ PositionCondition = ImGuiCond.FirstUseEver;
+ }
+
+ public override void PreDraw()
+ {
+ Vector4 bgColor = Setting.Locked ? Setting.LockedBackgroundColor : Setting.UnlockedBackgroundColor;
+ ImGui.PushStyleColor(ImGuiCol.WindowBg, bgColor);
+
+ Flags = _baseFlags;
+
+ if (Setting.Locked)
+ {
+ Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs;
+ }
+
+ ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
+ ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0);
+ }
+
+ public override void PostDraw()
+ {
+ ImGui.PopStyleColor();
+ ImGui.PopStyleVar(2);
+ }
+
+ public override void Draw()
+ {
+ if (ImGui.IsWindowHovered())
+ {
+ if (ImGui.IsMouseClicked(ImGuiMouseButton.Right))
+ {
+ Plugin.OpenConfigUi();
+ }
+ }
+ var pos = ImGui.GetWindowPos();
+ var size = ImGui.GetWindowSize();
+
+ var now = Setting.IsRotation ? TimelineManager.Instance?.EndTime ?? DateTime.Now : DateTime.Now;
+
+ var endTime = now - TimeSpan.FromSeconds(size.X / Setting.SizePerSecond - Setting.TimeOffset);
+
+ var last = now;
+ var list = TimelineManager.Instance?.GetItems(endTime, out last);
+
+ DrawGrid(pos, size);
+
+ if (Setting.ShowGCDClipping && list != null) //Clipping
+ {
+ var gcdClippingColor = ImGui.ColorConvertFloat4ToU32(Setting.GCDClippingColor);
+ var threshold = TimeSpan.FromSeconds(Setting.GCDClippingThreshold);
+ var max = TimeSpan.FromSeconds(Setting.GCDClippingMaxTime);
+ var sizePerSecond = Setting.SizePerSecond;
+
+ foreach (var item in list)
+ {
+ if (item.Type != TimelineItemType.GCD) continue;
+
+ var start = item.StartTime;
+ var span = start - last;
+
+ if (last != DateTime.MinValue && span >= threshold && span < max)
+ {
+ ImGui.GetWindowDrawList().AddRectFilled(
+ pos + new Vector2(size.X - (Setting.TimeOffset + (float)(now - last).TotalSeconds) * sizePerSecond, 0),
+ pos + new Vector2(size.X - (Setting.TimeOffset + (float)(now - start).TotalSeconds) * sizePerSecond, size.Y), gcdClippingColor);
+ }
+
+ last = item.EndTime;
+ }
+ }
+
+ if (list != null)
+ {
+ foreach (var item in list)
+ {
+ item.Draw(now, pos, size, TimelineLayer.General, Setting);
+ }
+ foreach (var item in list)
+ {
+ item.Draw(now, pos, size, TimelineLayer.Status, Setting);
+ }
+
+ var status = TimelineManager.Instance?.GetStatus(endTime, out _);
+ if (status != null && Setting.ShowStatusLine)
+ {
+ foreach (var item in status)
+ {
+ item.Draw(now, pos, size, Setting);
+ }
+ }
+
+ foreach (var item in list)
+ {
+ item.Draw(now, pos, size, TimelineLayer.Icon, Setting);
+ }
+ }
+
+ uint lineColor = ImGui.ColorConvertFloat4ToU32(Setting.GridStartLineColor);
+
+ var x = pos.X + size.X - Setting.TimeOffset * Setting.SizePerSecond;
+ ImGui.GetWindowDrawList().AddLine(new Vector2(x, pos.Y), new Vector2(x, pos.Y + size.Y), lineColor, Setting.GridStartLineWidth);
+ }
+
+ private void DrawGrid(Vector2 pos, Vector2 size)
+ {
+ if (!Setting.ShowGrid) return;
+
+ ImDrawListPtr drawList = ImGui.GetWindowDrawList();
+ float width = size.X;
+ float height = size.Y;
+
+ uint lineColor = ImGui.ColorConvertFloat4ToU32(Setting.GridLineColor);
+ uint subdivisionLineColor = ImGui.ColorConvertFloat4ToU32(Setting.GridSubdivisionLineColor);
+
+ if (Setting.ShowGridCenterLine)
+ {
+ drawList.AddLine(new Vector2(pos.X, pos.Y + height / 2f), new Vector2(pos.X + width, pos.Y + height / 2f), lineColor, Setting.GridLineWidth);
+ }
+
+ if (!Setting.GridDivideBySeconds) return;
+
+ float step = Setting.SizePerSecond;
+
+ for (int i = 0; i < width / step; i++)
+ {
+ float x = step * i;
+ var start = pos.X + width - x;
+
+ if (Setting.GridSubdivideSeconds && Setting.GridSubdivisionCount > 1)
+ {
+ float subStep = step * 1f / Setting.GridSubdivisionCount;
+ for (int j = 1; j < Setting.GridSubdivisionCount; j++)
+ {
+ drawList.AddLine(new Vector2(start + subStep * j, pos.Y), new Vector2(start + subStep * j, pos.Y + height), subdivisionLineColor, Setting.GridSubdivisionLineWidth);
+ }
+ }
+ var time = -i + Setting.TimeOffset;
+
+ if (time != 0)
+ {
+ drawList.AddLine(new Vector2(start, pos.Y), new Vector2(start, pos.Y + height), lineColor, Setting.GridLineWidth);
+ }
+
+ if (Setting.GridShowSecondsText)
+ {
+ drawList.AddText(new Vector2(start + 2, pos.Y), lineColor, $" {time}s");
+ }
+ }
+ return;
+ }
+}
diff --git a/ActionTimelineEx/packages.lock.json b/ActionTimelineEx/packages.lock.json
new file mode 100644
index 0000000..3d67fd2
--- /dev/null
+++ b/ActionTimelineEx/packages.lock.json
@@ -0,0 +1,16 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net7.0-windows7.0": {
+ "DalamudPackager": {
+ "type": "Direct",
+ "requested": "[2.1.11, )",
+ "resolved": "2.1.11",
+ "contentHash": "9qlAWoRRTiL/geAvuwR/g6Bcbrd/bJJgVnB/RurBiyKs6srsP0bvpoo8IK+Eg8EA6jWeM6/YJWs66w4FIAzqPw=="
+ },
+ "ecommons": {
+ "type": "Project"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/DalamudPackager.targets b/DalamudPackager.targets
deleted file mode 100644
index c05e473..0000000
--- a/DalamudPackager.targets
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ECommons b/ECommons
new file mode 160000
index 0000000..a59971b
--- /dev/null
+++ b/ECommons
@@ -0,0 +1 @@
+Subproject commit a59971b3db59b173c3069e3509a93f034c4b0832
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..c1af537
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,25 @@
+{
+ "Author": "Tischel, ArchiTed",
+ "Name": "ActionTimelineEx",
+ "InternalName": "ActionTimelineEx",
+ "Description": "Configurable timeline display of all the actions you use.",
+ "ApplicableVersion": "any",
+ "Tags": [
+ "UI"
+ ],
+ "CategoryTags": [
+ "UI"
+ ],
+ "DalamudApiLevel": 8,
+ "LoadRequiredState": 0,
+ "LoadSync": false,
+ "CanUnloadAsync": false,
+ "LoadPriority": 0,
+ "IsTestingExclusive": false,
+ "IconUrl": "https://xivapi.com/i/000000/000073_hr1.png",
+ "Punchline": "Show your actions in real-time.",
+ "ImageUrls":[
+ "https://raw.githubusercontent.com/ArchiDog1998/ActionTimelineEx/main/Images/example.gif"
+ ],
+ "AcceptsFeedback": true
+}
\ No newline at end of file