diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e445464..c88e4cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 7.0.* + dotnet-version: 8.0.* - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c144a8..21cca3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # v2.3.2.0 -- Adds +- Add an option to draw the custom 0-5 numbers without hiding the original countdown +- Make animation match vanilla a bit better # v2.3.1.0 @@ -59,4 +60,4 @@ - fix lag when starting a countdown for the first time after loading the plugin (will play a cursor noise when enabling the plugin, I couldn't find a way around it yet) - updated French and German translation thanks to contributors on [crowdin](https://crwd.in/engagetimer) -- internal reworking and optimizations \ No newline at end of file +- internal reworking and optimizations diff --git a/Plugin/Configuration/ConfigurationFile.cs b/Plugin/Configuration/ConfigurationFile.cs index 68241cf..cd32c4e 100644 --- a/Plugin/Configuration/ConfigurationFile.cs +++ b/Plugin/Configuration/ConfigurationFile.cs @@ -32,7 +32,7 @@ public enum TextAlign } // Add any other properties or methods here. - [NonSerialized] private DalamudPluginInterface _pluginInterface = Plugin.PluginInterface; + [NonSerialized] private IDalamudPluginInterface _pluginInterface = Plugin.PluginInterface; public CountdownConfiguration Countdown = new(); public DtrConfiguration Dtr = new(); diff --git a/Plugin/Configuration/FloatingWindowConfiguration.cs b/Plugin/Configuration/FloatingWindowConfiguration.cs index 9face6a..a87c2c9 100644 --- a/Plugin/Configuration/FloatingWindowConfiguration.cs +++ b/Plugin/Configuration/FloatingWindowConfiguration.cs @@ -16,7 +16,9 @@ #nullable enable using System; using System.Numerics; +using System.Text.Json.Serialization; using Dalamud.Interface.Colors; +using Dalamud.Interface.FontIdentifier; using EngageTimer.Attributes; using EngageTimer.Ui; @@ -48,12 +50,18 @@ public class FloatingWindowConfiguration [AutoField("Settings_FWTab_DisplayStopwatchOnlyInDuty")] public bool StopwatchOnlyInDuty { get; set; } = false; + + [AutoField("Settings_FWTab_HideInCutscenes")] + public bool HideInCutscenes { get; set; } = true; [AutoField("Settings_FWTab_StopwatchAsSeconds")] public bool StopwatchAsSeconds { get; set; } = false; [AutoField("Settings_FWTab_CountdownNegativeSign")] public bool CountdownNegativeSign { get; set; } = true; + + [AutoField("Settings_FWTab_ForceHideWindowBorder")] + public bool ForceHideWindowBorder { get; set; } = true; [AutoField("Settings_CountdownTab_FloatingWindowScale", Components.FieldType.DragFloat, .01f, .05f, 15f), ItemWidth(100f)] @@ -78,6 +86,9 @@ public class FloatingWindowConfiguration public ConfigurationFile.TextAlign Align { get; set; } = ConfigurationFile.TextAlign.Left; public int FontSize { get; set; } = 16; + + // [JsonIgnore] public IFontSpec? FontSpec { get; set; } = null; + // public IFontSpec? Font { get; set; } = null; [AutoField("Settings_FWTab_AutoHide_Left")] public bool AutoHide { get; set; } = true; diff --git a/Plugin/EngageTimer.csproj b/Plugin/EngageTimer.csproj index 75cbbf8..537ba52 100644 --- a/Plugin/EngageTimer.csproj +++ b/Plugin/EngageTimer.csproj @@ -1,14 +1,14 @@ - net7.0-windows + net8.0-windows x64 AnyCPU - 10 + 12 true false false - 2.3.2.0 + 2.4.1.0 false true true @@ -17,6 +17,8 @@ true EngageTimer enable + AGPL-3.0-or-later + false @@ -65,7 +67,7 @@ - + @@ -90,6 +92,15 @@ $(appdata)\XIVLauncher\addon\Hooks\dev\ + true + + + + $(DALAMUD_HOME)/ + $(appdata)\XIVLauncher\addon\Hooks\dev\ + $(HOME)/.xlcore/dalamud/Hooks/dev/ + $(HOME)/Library/Application Support/XIV on Mac/dalamud/Hooks/dev/ + $(DALAMUD_HOME)/ diff --git a/Plugin/EngageTimer.yaml b/Plugin/EngageTimer.yaml index ba189bf..4e19750 100644 --- a/Plugin/EngageTimer.yaml +++ b/Plugin/EngageTimer.yaml @@ -33,4 +33,5 @@ image_urls: category_tags: - jobs changelog: |- - - Add an option to draw the custom 0-5 numbers without hiding the original countdown \ No newline at end of file + - Option to hide the stopwatch/floating window in cutscenes + - Enabling light effect animation would show an error texture and required a plugin restart to be loaded properly \ No newline at end of file diff --git a/Plugin/Game/AddonHider.cs b/Plugin/Game/AddonHider.cs index a19dfa1..3074373 100644 --- a/Plugin/Game/AddonHider.cs +++ b/Plugin/Game/AddonHider.cs @@ -46,7 +46,8 @@ private unsafe void ShowAddonNearEnd(object? sender, EventArgs eventArgs) try { var atkUnitBase = (AtkUnitBase*)_lastCountdownAddon; - atkUnitBase->Flags |= VisibleFlag; + atkUnitBase->IsVisible = true; + // atkUnitBase->Flags |= VisibleFlag; // Plugin.Logger.Debug("show addon"); } catch (Exception) @@ -74,7 +75,8 @@ private unsafe void HideAddon(AddonEvent type, AddonArgs args) try { var atkUnitBase = (AtkUnitBase*)addon; - atkUnitBase->Flags = (byte)(atkUnitBase->Flags & ~VisibleFlag); + atkUnitBase->IsVisible = false; + // atkUnitBase->Flags = (byte)(atkUnitBase->Flags & ~VisibleFlag); // Plugin.Logger.Debug("hide addon"); } catch (Exception) diff --git a/Plugin/Game/CountdownHook.cs b/Plugin/Game/CountdownHook.cs index b87e913..cabbfe4 100644 --- a/Plugin/Game/CountdownHook.cs +++ b/Plugin/Game/CountdownHook.cs @@ -23,31 +23,24 @@ /* * Based on the work (for finding the pointer) of https://github.com/Haplo064/Europe + * 7.0 function changes taken from https://github.com/DelvUI/DelvUI/commit/492211c8f43b813d10b2220e6fe768ac508dcede and + * https://discord.com/channels/581875019861328007/653504487352303619/1257920418388639774. Thanks Tischel for that work! */ namespace EngageTimer.Game; public sealed class CountdownHook : IDisposable { - [Signature("48 89 5C 24 ?? 57 48 83 EC 40 8B 41", DetourName = nameof(CountdownTimerFunc))] + [Signature("40 53 48 83 EC 40 80 79 38 00", DetourName = nameof(CountdownTimerFunc))] private readonly Hook? _countdownTimerHook = null; private readonly State _state; - private ulong _countDown; - private bool _countDownRunning; - - /// - /// Ticks since the timer stalled - /// - private int _countDownStallTicks; - - private float _lastCountDownValue; - + private ulong _paramValue; public CountdownHook() { _state = Plugin.State; - _countDown = 0; + _paramValue = 0; Plugin.GameInterop.InitializeFromAttributes(this); _countdownTimerHook?.Enable(); } @@ -61,7 +54,7 @@ public void Dispose() private IntPtr CountdownTimerFunc(ulong value) { - _countDown = value; + _paramValue = value; return _countdownTimerHook!.Original(value); } @@ -70,41 +63,16 @@ public void Update() if (_state.Mocked) return; UpdateCountDown(); _state.InInstance = Plugin.Condition[ConditionFlag.BoundByDuty]; + _state.InCutscene = Plugin.Condition[ConditionFlag.OccupiedInCutSceneEvent]; } private void UpdateCountDown() { - var newCountingDown = false; - if (_countDown == 0) - { - _state.CountingDown = newCountingDown; - return; - } - - var countDownPointerValue = Marshal.PtrToStructure((IntPtr)_countDown + 0x2c); - - // is last value close enough (workaround for floating point approx) - if (Math.Abs(countDownPointerValue - _lastCountDownValue) < 0.001f) - { - _countDownStallTicks++; - } - else - { - _countDownStallTicks = 0; - _countDownRunning = true; - } - - if (_countDownStallTicks > 50) _countDownRunning = false; - - if (countDownPointerValue > 0 && _countDownRunning) - { - var newValue = Marshal.PtrToStructure((IntPtr)_countDown + 0x2c); - _state.CountDownValue = newValue; - newCountingDown = true; - } - - _state.CountingDown = newCountingDown; - _lastCountDownValue = countDownPointerValue; + if (_paramValue == 0) return; + var countDownActive = Marshal.PtrToStructure((IntPtr)_paramValue + 0x38) == 1; + var countDownPointerValue = Marshal.PtrToStructure((IntPtr)_paramValue + 0x2c); + _state.CountingDown = countDownActive && countDownPointerValue > 0f; + _state.CountDownValue = countDownPointerValue; } [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)] diff --git a/Plugin/Game/SFXPlay.cs b/Plugin/Game/SFXPlay.cs index 351a97c..5e25544 100644 --- a/Plugin/Game/SFXPlay.cs +++ b/Plugin/Game/SFXPlay.cs @@ -21,12 +21,13 @@ namespace EngageTimer.Game; /** * thanks aers * sig taken from https://github.com/philpax/plogonscript/blob/main/PlogonScript/Script/Bindings/Sound.cs + * https://github.com/0ceal0t/JobBars/blob/2c9bef8dd4f0bf9ebc91c07e03da6c841ac2bd35/JobBars/Helper/UiHelper.GameFunctions.cs#L61 * --- * https://discord.com/channels/581875019861328007/653504487352303619/988123102116450335 */ internal unsafe class GameSound { - [Signature("E8 ?? ?? ?? ?? 4D 39 BE ?? ?? ?? ??")] + [Signature("E8 ?? ?? ?? ?? 48 63 45 80")] public readonly delegate* unmanaged PlaySoundEffect = null; public GameSound() @@ -45,10 +46,10 @@ public class SfxPlay public SfxPlay() { /* Force a sound to play on load as a workaround for the CLR taking some time to init the pointy method call, - * we dont want a freeze midway through a countdown + * we don't want a freeze midway through a countdown (or midway in combat for alarms) * https://discord.com/channels/581875019861328007/653504487352303619/988123102116450335 * https://i.imgur.com/BrLUr2p.png - * */ + */ SoundEffect(0); // should be cursor sound } diff --git a/Plugin/Plugin.cs b/Plugin/Plugin.cs index ba36161..3287c7e 100644 --- a/Plugin/Plugin.cs +++ b/Plugin/Plugin.cs @@ -30,7 +30,7 @@ namespace EngageTimer; [PublicAPI] public sealed class Plugin : IDalamudPlugin { - [PluginService] public static DalamudPluginInterface PluginInterface { get; private set; } = null!; + [PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null!; [PluginService] public static IGameGui GameGui { get; private set; } = null!; [PluginService] public static ICommandManager Commands { get; private set; } = null!; [PluginService] public static ICondition Condition { get; private set; } = null!; @@ -42,6 +42,8 @@ public sealed class Plugin : IDalamudPlugin [PluginService] public static IPluginLog Logger { get; private set; } = null!; [PluginService] public static IAddonLifecycle AddonLifecycle { get; private set; } = null!; [PluginService] public static IToastGui ToastGui { get; private set; } = null!; + [PluginService] public static INotificationManager NotificationManager { get; private set; } = null!; + [PluginService] public static ITextureProvider TextureProvider { get; private set; } = null!; public static ConfigurationFile Config { get; private set; } = null!; public static State State { get; private set; } = null!; public static PluginUi PluginUi { get; private set; } = null!; @@ -52,8 +54,9 @@ public sealed class Plugin : IDalamudPlugin private static SettingsCommand SettingsCommand { get; set; } = null!; public static CombatAlarm CombatAlarm { get; set; } = null!; public static SfxPlay SfxPlay { get; set; } = null!; + public static FloatingWindowFont FloatingWindowFont { get; set; } = null!; - public Plugin(DalamudPluginInterface pluginInterface) + public Plugin(IDalamudPluginInterface pluginInterface) { PluginPath = PluginInterface.AssemblyLocation.DirectoryName ?? throw new InvalidOperationException("Cannot find plugin directory"); @@ -63,6 +66,7 @@ public Plugin(DalamudPluginInterface pluginInterface) FrameworkThings = new FrameworkThings(); MainCommand = new MainCommand(); SettingsCommand = new SettingsCommand(); + FloatingWindowFont = new FloatingWindowFont(); PluginUi = new PluginUi(); CombatAlarm = new CombatAlarm(); SfxPlay = new SfxPlay(); @@ -77,5 +81,6 @@ void IDisposable.Dispose() FrameworkThings.Dispose(); MainCommand.Dispose(); SettingsCommand.Dispose(); + FloatingWindowFont.Dispose(); } } \ No newline at end of file diff --git a/Plugin/Properties/Strings.en.resx b/Plugin/Properties/Strings.en.resx index d4bdc6b..3694dd6 100644 --- a/Plugin/Properties/Strings.en.resx +++ b/Plugin/Properties/Strings.en.resx @@ -521,4 +521,7 @@ help text for the /eg settings command About + + Hide window border + \ No newline at end of file diff --git a/Plugin/Status/CombatAlarm.cs b/Plugin/Status/CombatAlarm.cs index b9bd796..173f930 100644 --- a/Plugin/Status/CombatAlarm.cs +++ b/Plugin/Status/CombatAlarm.cs @@ -18,7 +18,7 @@ using System.IO; using System.Linq; using Dalamud.Game.Text; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin.Services; using EngageTimer.Configuration; using EngageTimer.Game; @@ -118,6 +118,7 @@ private void ConfigurationChanged(object? sender, EventArgs e) for (var index = 0; index < Plugin.Config.CombatAlarms.Alarms.Count; index++) { var alarm = Plugin.Config.CombatAlarms.Alarms[index]; + if (!alarm.Enabled) continue; _alarms[alarm.StartTime] = new AlarmAction() { Type = AlarmActionType.Start, @@ -148,7 +149,7 @@ private void FrameworkUpdate(IFramework framework) if (!Plugin.State.InCombat) return; // only run once a second - var time = (int) Math.Floor(Plugin.State.CombatDuration.TotalSeconds); + var time = (int)Math.Floor(Plugin.State.CombatDuration.TotalSeconds); if (_lastCheck == time) return; _lastCheck = time; @@ -167,22 +168,25 @@ public static void AlarmSfx(CombatAlarmsConfiguration.Alarm alarm) { if (alarm.Sfx != null) { - Plugin.SfxPlay.SoundEffect((uint) (SfxPlay.FirstSeSfx + alarm.Sfx)); + Plugin.SfxPlay.SoundEffect((uint)(SfxPlay.FirstSeSfx + alarm.Sfx)); } } public static void AlarmText(CombatAlarmsConfiguration.Alarm alarm) { var trimText = alarm.Text?.Trim(); - if (trimText is not {Length: > 0}) return; + if (trimText is not { Length: > 0 }) return; switch (alarm.TextType) { case CombatAlarmsConfiguration.TextType.DalamudNotification: - Plugin.PluginInterface.UiBuilder.AddNotification( - trimText, - "EngageTimer", - NotificationType.Info, - 8000 + Plugin.NotificationManager.AddNotification( + new Notification() + { + Content = trimText, + Title ="EngageTimer", + Type = NotificationType.Info, + InitialDuration = TimeSpan.FromSeconds(8.0) + } ); break; case CombatAlarmsConfiguration.TextType.GameToast: diff --git a/Plugin/Status/CombatStopwatch.cs b/Plugin/Status/CombatStopwatch.cs index a4fcf54..e3da2db 100644 --- a/Plugin/Status/CombatStopwatch.cs +++ b/Plugin/Status/CombatStopwatch.cs @@ -19,6 +19,7 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin.Services; using EngageTimer.Configuration; +using FFXIVClientStructs.FFXIV.Client.Game.Character; namespace EngageTimer.Status; @@ -43,7 +44,7 @@ public void UpdateEncounterTimer() // if anyone in the party is in combat foreach (var actor in Plugin.PartyList) { - if (actor.GameObject is not Character character || + if (actor.GameObject is not ICharacter character || (character.StatusFlags & StatusFlags.InCombat) == 0) continue; inCombat = true; break; diff --git a/Plugin/Status/State.cs b/Plugin/Status/State.cs index dc080a5..f82c171 100644 --- a/Plugin/Status/State.cs +++ b/Plugin/Status/State.cs @@ -22,6 +22,7 @@ public class State { private bool _countingDown; private bool _inCombat; + public bool InCutscene { get; set; } public TimeSpan CombatDuration { get; set; } public DateTime CombatEnd { get; set; } public DateTime CombatStart { get; set; } diff --git a/Plugin/Ui/Components.cs b/Plugin/Ui/Components.cs index e29db8b..b708d5e 100644 --- a/Plugin/Ui/Components.cs +++ b/Plugin/Ui/Components.cs @@ -162,6 +162,7 @@ private static void ShowField(T instance, FieldType? foundType, PropertyInfo if (!ImGui.Checkbox(label, ref value)) return; prop.SetValue(instance, value); Plugin.Config.Save(); + customApply?.Invoke(); } else if (foundType == FieldType.InputInt) { diff --git a/Plugin/Ui/CountDown.cs b/Plugin/Ui/CountDown.cs index 70ae630..5ebd78a 100644 --- a/Plugin/Ui/CountDown.cs +++ b/Plugin/Ui/CountDown.cs @@ -43,7 +43,7 @@ public sealed class CountDown : IDisposable * * In my testing, this is about 10 to 20ms. */ - private bool _firstDraw = true; + private int _firstDrawTicks = 5; private int _lastSecond; private bool _wasInMainViewport = true; @@ -88,9 +88,14 @@ private void FirstDraw() if (ImGui.Begin(WindowTitle, ref visible, flags)) { ImGui.Text(""); + for (var i = 0; i <= 9; i++) + { + DrawNumber(false, i, 0.001f, 0f, 1f, false); + DrawNumber(true, i, 0.001f, 0f, 1f, false); + } } - _firstDraw = false; + _firstDrawTicks--; } public void Draw() @@ -112,7 +117,12 @@ public void Draw() // ImGui.End(); // #endif - if (_firstDraw) FirstDraw(); + if (_firstDrawTicks > 0) + { + FirstDraw(); + return; + } + if (!Plugin.Config.Countdown.Display || !Plugin.State.CountingDown) return; var showMainCountdown = Plugin.Config.Countdown.HideOriginalAddon || @@ -130,7 +140,7 @@ public void Draw() if (Plugin.Config.Countdown.Animate) { - var second = (int) Plugin.State.CountDownValue; + var second = (int)Plugin.State.CountDownValue; if (_lastSecond != second) { _easing.Restart(); @@ -143,7 +153,7 @@ public void Draw() if (Plugin.Config.Countdown.AnimateScale) { maxNumberScale = numberScale + NumberEasing.StartSize; - numberScale += NumberEasing.StartSize * (1 - (float) _easing.Value); + numberScale += NumberEasing.StartSize * (1 - (float)_easing.Value); } } } @@ -190,7 +200,7 @@ public void Draw() ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), ImGui.GetColorU32(ImGuiCol.Text), 0f, ImDrawFlags.None, - 7f + (float) Math.Sin(ImGui.GetTime() * 2) * 5f); + 7f + (float)Math.Sin(ImGui.GetTime() * 2) * 5f); d.AddRect( ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), @@ -202,7 +212,7 @@ public void Draw() DrawCountdown(showMainCountdown, numberScale, negativeMargin, false); if (Plugin.Config.Countdown.Animate && Plugin.Config.Countdown.AnimateOpacity) { - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, (float) _easingOpacity.Value); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, (float)_easingOpacity.Value); DrawCountdown(showMainCountdown, numberScale, negativeMargin, true); ImGui.PopStyleVar(); } diff --git a/Plugin/Ui/DtrBarUi.cs b/Plugin/Ui/DtrBarUi.cs index ae3ecb5..68b8261 100644 --- a/Plugin/Ui/DtrBarUi.cs +++ b/Plugin/Ui/DtrBarUi.cs @@ -21,7 +21,7 @@ namespace EngageTimer.Ui; public sealed class DtrBarUi : IDisposable { - private DtrBarEntry? _entry; + private IDtrBarEntry? _entry; public DtrBarUi() { @@ -31,10 +31,10 @@ public DtrBarUi() public void Dispose() { - _entry?.Dispose(); + _entry?.Remove(); } - private DtrBarEntry? GetEntry() + private IDtrBarEntry? GetEntry() { const string dtrBarTitle = "EngageTimer stopwatch"; var dtrBar = Plugin.DtrBar; @@ -89,6 +89,7 @@ public void Update() if (_entry is { Shown: true }) _entry.Shown = false; return; } + if (!_entry.Shown) _entry.Shown = true; var seString = (SeString)(Plugin.Config.Dtr.CombatTimePrefix + diff --git a/Plugin/Ui/FloatingWindow.cs b/Plugin/Ui/FloatingWindow.cs index 3b82c11..1884269 100644 --- a/Plugin/Ui/FloatingWindow.cs +++ b/Plugin/Ui/FloatingWindow.cs @@ -15,34 +15,20 @@ using System; using System.Globalization; -using System.IO; using System.Numerics; -using Dalamud.Interface; using EngageTimer.Configuration; -using EngageTimer.Status; using ImGuiNET; namespace EngageTimer.Ui; -public sealed class FloatingWindow : IDisposable +public sealed class FloatingWindow { private const float WindowPadding = 5f; - private bool _firstLoad = true; - private ImFontPtr _font; - private ImFontGlyphRangesBuilderPtr? _grBuilder; - private float _maxTextWidth; private float _paddingLeft; private float _paddingRight; private bool _stopwatchVisible; - private bool _triggerFontRebuild = true; - private bool _useFont; - - public FloatingWindow() - { - Plugin.PluginInterface.UiBuilder.BuildFonts += BuildFont; - } public bool StopwatchVisible { @@ -50,32 +36,18 @@ public bool StopwatchVisible set => _stopwatchVisible = value; } - public void Dispose() - { - Plugin.PluginInterface.UiBuilder.BuildFonts -= BuildFont; - _grBuilder?.Destroy(); - Plugin.PluginInterface.UiBuilder.RebuildFonts(); - } - public void Draw() { if (!Plugin.Config.FloatingWindow.Display) return; - if (_triggerFontRebuild) - { - Plugin.PluginInterface.UiBuilder.RebuildFonts(); - return; - } - var stopwatchActive = StopwatchActive(); var countdownActive = CountdownActive(); - if (!_firstLoad && !stopwatchActive && !countdownActive) return; - - if (_useFont && _font.IsLoaded()) ImGui.PushFont(_font); - DrawWindow(stopwatchActive, countdownActive); - if (_useFont && _font.IsLoaded()) ImGui.PopFont(); + if (!stopwatchActive && !countdownActive) return; - if (_firstLoad) _firstLoad = false; + using (Plugin.FloatingWindowFont.FontHandle?.Push()) + { + DrawWindow(stopwatchActive, countdownActive); + } } private static bool StopwatchActive() @@ -83,6 +55,8 @@ private static bool StopwatchActive() var displayStopwatch = Plugin.Config.FloatingWindow.EnableStopwatch; if (!displayStopwatch) return false; + if (Plugin.Config.FloatingWindow.HideInCutscenes && Plugin.State.InCutscene) return false; + if (Plugin.Config.FloatingWindow.AutoHide && (DateTime.Now - Plugin.State.CombatEnd).TotalSeconds > Plugin.Config.FloatingWindow.AutoHideTimeout) return false; @@ -99,9 +73,18 @@ private static bool CountdownActive() private void DrawWindow(bool stopwatchActive, bool countdownActive) { // ImGui.SetNextWindowBgAlpha(_configuration.FloatingWindow.FloatingWindowBackgroundColor.Z); + var pushVar = false; + if (Plugin.Config.FloatingWindow.ForceHideWindowBorder) + { + // prevent glitches is user is spamming the checkbox button + pushVar = true; + ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0); + } + ImGui.PushStyleColor(ImGuiCol.WindowBg, Plugin.Config.FloatingWindow.BackgroundColor); - var flags = ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoScrollbar; + var flags = ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoScrollbar | + ImGuiWindowFlags.NoFocusOnAppearing; if (Plugin.Config.FloatingWindow.Lock) flags |= ImGuiWindowFlags.NoMouseInputs; if (ImGui.Begin("EngageTimer stopwatch", ref _stopwatchVisible, flags)) @@ -136,6 +119,7 @@ private void DrawWindow(bool stopwatchActive, bool countdownActive) var format = "{0:0." + new string('0', Plugin.Config.FloatingWindow.DecimalCountdownPrecision) + "}"; var number = Plugin.State.CountDownValue + (Plugin.Config.FloatingWindow.AccurateMode ? 0 : 1); + if (Plugin.Config.FloatingWindow.DecimalCountdownPrecision == 0) number = (float)Math.Floor(number); text = negative + string.Format(CultureInfo.InvariantCulture, format, number); displayed = true; } @@ -185,7 +169,6 @@ private void DrawWindow(bool stopwatchActive, bool countdownActive) _paddingRight = 0f; } - var size = ImGui.CalcTextSize(text); ImGui.SetCursorPosY(0f); ImGui.SetCursorPosX(_paddingLeft + WindowPadding); @@ -207,49 +190,6 @@ private void DrawWindow(bool stopwatchActive, bool countdownActive) } ImGui.PopStyleColor(); - } - - - /** - * UI font code adapted from ping plugin by karashiiro - * https://github.com/karashiiro/PingPlugin/blob/feex/PingPlugin/PingUI.cs - */ - private unsafe void BuildFont() - { - _triggerFontRebuild = false; - - _grBuilder?.Destroy(); - try - { - // attempt to load the correct font (I'm only using numbers anyway) - string[] fonts = { "NotoSansCJKsc-Medium.otf", "NotoSansCJKjp-Medium.otf" }; - string? filePath = null; - foreach (var font in fonts) - { - filePath = Path.Combine(Plugin.PluginInterface.DalamudAssetDirectory.FullName, "UIRes", font); - if (File.Exists(filePath)) break; - filePath = null; - } - - if (filePath == null) throw new FileNotFoundException("Font file not found!"); - - var grBuilder = - new ImFontGlyphRangesBuilderPtr(ImGuiNative.ImFontGlyphRangesBuilder_ImFontGlyphRangesBuilder()); - // the "z" at the end of this range is still required because of a dalamud issue that somehow removes the - // ":" from my text range. - grBuilder.AddText("-0123456789:.z"); - grBuilder.BuildRanges(out var ranges); - _font = ImGui.GetIO().Fonts.AddFontFromFileTTF(filePath, - Math.Max(8, Plugin.Config.FloatingWindow.FontSize), - null, ranges.Data); - - _grBuilder = grBuilder; - } - catch (Exception e) - { - Plugin.Logger.Error(e.Message); - } - - _useFont = true; + if (pushVar) ImGui.PopStyleVar(); } } \ No newline at end of file diff --git a/Plugin/Ui/FloatingWindowFont.cs b/Plugin/Ui/FloatingWindowFont.cs new file mode 100644 index 0000000..17aeb33 --- /dev/null +++ b/Plugin/Ui/FloatingWindowFont.cs @@ -0,0 +1,70 @@ +// This file is part of EngageTimer +// Copyright (C) 2024 Xorus +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using Dalamud.Interface.ManagedFontAtlas; + +namespace EngageTimer.Ui; + +public sealed class FloatingWindowFont : IDisposable +{ + public IFontHandle? FontHandle { get; private set; } + + public FloatingWindowFont() + { + UpdateFont(); + } + + public void UpdateFont() + { + this.FontHandle?.Dispose(); + this.FontHandle = Plugin.PluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e => e.OnPreBuild(tk => + { + tk.AddDalamudDefaultFont( + Math.Max(8, Plugin.Config.FloatingWindow.FontSize), + FontAtlasBuildToolkitUtilities.ToGlyphRange([ + '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '.' + ]) + ); + // var spec = (SingleFontSpec)(Plugin.Config.FloatingWindow.FontSpec ?? + // Plugin.PluginInterface.UiBuilder.DefaultFontSpec); + // var specWithRanges = new SingleFontSpec() + // { + // FontId = spec.FontId, + // SizePx = spec.SizePx, + // SizePt = spec.SizePt, + // GlyphOffset = spec.GlyphOffset, + // LetterSpacing = spec.LetterSpacing, + // LineHeight = spec.LineHeight, + // GlyphRanges = FontAtlasBuildToolkitUtilities.ToGlyphRange([ + // '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '.' + // ]) + // }; + + // var spec = Plugin.PluginInterface.UiBuilder.DefaultFontSpec; + // spec.AddToBuildToolkit(tk, font); + /// fontAtlas.NewDelegateFontHandle( + /// e => e.OnPreBuild( + /// tk => tk.AddDalamudDefaultFont(UiBuilder.DefaultFontSizePx))); + })); + + // this.FontHandle = Plugin.PluginInterface.UiBuilder.DefaultFontHandle + } + + public void Dispose() + { + FontHandle?.Dispose(); + } +} \ No newline at end of file diff --git a/Plugin/Ui/NumberTextures.cs b/Plugin/Ui/NumberTextures.cs index 269f83f..3ffd49e 100644 --- a/Plugin/Ui/NumberTextures.cs +++ b/Plugin/Ui/NumberTextures.cs @@ -15,10 +15,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using Dalamud.Interface; -using Dalamud.Interface.Internal; +using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures.TextureWraps; using EngageTimer.Configuration; using EngageTimer.Ui.Color; using Newtonsoft.Json; @@ -33,13 +35,15 @@ public sealed class NumberTextures private readonly Dictionary _numberImages = new(); private readonly Dictionary _numberTextures = new(); private readonly Dictionary _numberTexturesAlt = new(); - private readonly UiBuilder _uiBuilder = Plugin.PluginInterface.UiBuilder; + private readonly IUiBuilder _uiBuilder = Plugin.PluginInterface.UiBuilder; public double LastTextureCreationDuration = 0d; public NumberTextures() { - _error = _uiBuilder.LoadImage(Path.Combine(Plugin.PluginPath, "Data", "error.png")); + // plugin crash if the default file is not found is expected + _error = Plugin.TextureProvider.CreateFromImageAsync(File.OpenRead(Path.Combine(Plugin.PluginPath, "Data", + "error.png"))).Result; Load(); } @@ -123,7 +127,7 @@ private void ReadPackSettings(string settingsFile) catch (Exception exception) { Plugin.Logger.Warning("Invalid json or missing property in " + settingsFile + "\n" + - exception); + exception); } } catch (IOException) @@ -140,37 +144,37 @@ private void ReadPackSettings(string settingsFile) public void CreateTextures() { - var watch = System.Diagnostics.Stopwatch.StartNew(); + var watch = Stopwatch.StartNew(); MaxTextureHeight = 0; MaxTextureWidth = 0; + + var createAltTextures = Plugin.Config.Countdown.Animate; var success = false; for (var i = 0; i < 10; i++) { - if (_numberImages.ContainsKey(i)) + if (_numberImages.TryGetValue(i, out var image)) try { - var image = _numberImages[i]; var bytes = image.Data.ToArray(); var bytesAlt = new byte[bytes.Length]; - var configuration = Plugin.Config; if (image.NumChannels == 4) for (var p = 0; p < bytes.Length; p += 4) { var originalRgb = new HslConv.Rgb(bytes[p], bytes[p + 1], bytes[p + 2]); var hsl = HslConv.RgbToHsl(originalRgb); - if (configuration.Countdown.NumberRecolorMode) - hsl.H = Math.Clamp(configuration.Countdown.Hue, 0, 360); + if (Plugin.Config.Countdown.NumberRecolorMode) + hsl.H = Math.Clamp(Plugin.Config.Countdown.Hue, 0, 360); else - hsl.H += configuration.Countdown.Hue; - hsl.S = Math.Clamp(hsl.S + configuration.Countdown.Saturation, 0f, 1f); - hsl.L = Math.Clamp(hsl.L + configuration.Countdown.Luminance, 0f, 1f); + hsl.H += Plugin.Config.Countdown.Hue; + hsl.S = Math.Clamp(hsl.S + Plugin.Config.Countdown.Saturation, 0f, 1f); + hsl.L = Math.Clamp(hsl.L + Plugin.Config.Countdown.Luminance, 0f, 1f); var modifiedRgb = HslConv.HslToRgb(hsl); bytes[p] = modifiedRgb.R; bytes[p + 1] = modifiedRgb.G; bytes[p + 2] = modifiedRgb.B; - if (!configuration.Countdown.Animate) continue; + if (!createAltTextures) continue; var hslAlt = new HslConv.Hsl(hsl.H, hsl.S, hsl.L); hslAlt.L = Math.Clamp(hslAlt.L + .3f, 0f, 1f); var modifiedRgbAlt = HslConv.HslToRgb(hslAlt); @@ -180,9 +184,13 @@ public void CreateTextures() bytesAlt[p + 3] = bytes[p + 3]; } - var texture = _uiBuilder.LoadImageRaw(bytes, image.Width, image.Height, image.NumChannels); - var textureAlt = - _uiBuilder.LoadImageRaw(bytesAlt, image.Width, image.Height, image.NumChannels); + + var texture = Plugin.TextureProvider.CreateFromRaw( + RawImageSpecification.Rgba32(image.Width, image.Height), + bytes); + var textureAlt = Plugin.TextureProvider.CreateFromRaw( + RawImageSpecification.Rgba32(image.Width, image.Height), + bytesAlt); MaxTextureHeight = Math.Max(MaxTextureHeight, texture.Height); MaxTextureWidth = Math.Max(MaxTextureWidth, texture.Width); @@ -190,7 +198,7 @@ public void CreateTextures() _numberTextures.Add(i, texture); success = true; - if (!configuration.Countdown.Animate) continue; + if (!createAltTextures) continue; _numberTexturesAlt.Remove(i); _numberTexturesAlt.Add(i, textureAlt); } @@ -210,11 +218,11 @@ public void CreateTextures() public IDalamudTextureWrap GetTexture(int i) { - return _numberTextures.ContainsKey(i) ? _numberTextures[i] : _error; + return _numberTextures.GetValueOrDefault(i, _error); } public IDalamudTextureWrap GetAltTexture(int i) { - return _numberTexturesAlt.ContainsKey(i) ? _numberTexturesAlt[i] : _error; + return _numberTexturesAlt.GetValueOrDefault(i, _error); } } \ No newline at end of file diff --git a/Plugin/Ui/PluginUi.cs b/Plugin/Ui/PluginUi.cs index 619531f..4c78dd4 100644 --- a/Plugin/Ui/PluginUi.cs +++ b/Plugin/Ui/PluginUi.cs @@ -37,7 +37,6 @@ public PluginUi() public void Dispose() { - _floatingWindow.Dispose(); _countDown.Dispose(); Plugin.PluginInterface.UiBuilder.Draw -= Draw; Plugin.PluginInterface.UiBuilder.OpenConfigUi -= OpenSettings; diff --git a/Plugin/Ui/SettingsTab/AlarmsTab.cs b/Plugin/Ui/SettingsTab/AlarmsTab.cs index 8bc5116..8929246 100644 --- a/Plugin/Ui/SettingsTab/AlarmsTab.cs +++ b/Plugin/Ui/SettingsTab/AlarmsTab.cs @@ -179,7 +179,7 @@ private static void AlarmElement(int index, CombatAlarmsConfiguration.Alarm alar } { ImGui.TableNextColumn(); - ImGui.Checkbox("###enabled" + index, ref alarm.Enabled); + if (ImGui.Checkbox("###enabled" + index, ref alarm.Enabled)) Plugin.Config.Save(); } { ImGui.TableNextColumn(); @@ -211,10 +211,7 @@ private static void AlarmElement(int index, CombatAlarmsConfiguration.Alarm alar } { ImGui.TableNextColumn(); - if (ImGui.Checkbox("###blink", ref alarm.Blink)) - { - Plugin.Config.Save(); - } + if (ImGui.Checkbox("###blink", ref alarm.Blink)) Plugin.Config.Save(); } { ImGui.TableNextColumn(); @@ -248,13 +245,13 @@ private static void AlarmElement(int index, CombatAlarmsConfiguration.Alarm alar ImGui.TableNextColumn(); if (EditingTexts.Contains(index)) { - var type = (int) alarm.TextType; + var type = (int)alarm.TextType; ImGui.PushItemWidth(150f); if (ImGui.Combo("Type", ref type, Strings.AlarmEdit_Type_ChatLog + "\0" + Strings.AlarmEdit_Type_DalamudNotification + "\0" + Strings.AlarmEdit_Type_GameToast + "\0")) { - alarm.TextType = (CombatAlarmsConfiguration.TextType) type; + alarm.TextType = (CombatAlarmsConfiguration.TextType)type; Plugin.Config.Save(); } diff --git a/Plugin/Ui/SettingsTab/CountdownTab.cs b/Plugin/Ui/SettingsTab/CountdownTab.cs index 0ac122a..8c26f5b 100644 --- a/Plugin/Ui/SettingsTab/CountdownTab.cs +++ b/Plugin/Ui/SettingsTab/CountdownTab.cs @@ -48,7 +48,6 @@ public static void DebounceTextureCreation() private static bool _mocking; - private static double _mockStart; private static double _mockTarget; public static void UpdateMock() @@ -57,7 +56,7 @@ public static void UpdateMock() if (_mockTarget == 0 || _mockTarget < ImGui.GetTime()) _mockTarget = ImGui.GetTime() + 30d; Plugin.State.CountingDown = true; - Plugin.State.CountDownValue = (float) (_mockTarget - ImGui.GetTime()); + Plugin.State.CountDownValue = (float)(_mockTarget - ImGui.GetTime()); } public static void OnClose() @@ -75,7 +74,6 @@ private static void ToggleMock() Plugin.State.InCombat = false; Plugin.State.CountDownValue = 12.23f; Plugin.State.CountingDown = true; - _mockStart = ImGui.GetTime(); } else { @@ -146,7 +144,7 @@ public static void Draw() ImGui.Unindent(); } - public static void CountdownHideOptions() + private static void CountdownHideOptions() { var cdStatus = 0; if (Plugin.Config.Countdown.HideOriginalAddon) cdStatus = 1; @@ -202,7 +200,7 @@ private static void CountdownPositionAndSize() if (ImGui.DragFloat("Settings_CountdownTab_OffsetX".TrId(), ref countdownOffsetX, .1f)) { Plugin.Config.Countdown.WindowOffset = - Plugin.Config.Countdown.WindowOffset with {X = countdownOffsetX / 100}; + Plugin.Config.Countdown.WindowOffset with { X = countdownOffsetX / 100 }; Plugin.Config.Save(); } @@ -212,7 +210,7 @@ private static void CountdownPositionAndSize() if (ImGui.DragFloat("Settings_CountdownTab_OffsetY".TrId(), ref countdownOffsetY, .1f)) { Plugin.Config.Countdown.WindowOffset = - Plugin.Config.Countdown.WindowOffset with {Y = countdownOffsetY / 100}; + Plugin.Config.Countdown.WindowOffset with { Y = countdownOffsetY / 100 }; Plugin.Config.Save(); } @@ -236,13 +234,13 @@ private static void CountdownPositionAndSize() ImGui.PopItemWidth(); - var align = (int) Plugin.Config.Countdown.Align; + var align = (int)Plugin.Config.Countdown.Align; if (ImGui.Combo("Settings_CountdownTab_CountdownAlign".TrId(), ref align, "Settings_FWTab_TextAlign_Left".Tr() + "###Left\0" + "Settings_FWTab_TextAlign_Center".Tr() + "###Center\0" + "Settings_FWTab_TextAlign_Right".Tr() + "###Right")) { - Plugin.Config.Countdown.Align = (ConfigurationFile.TextAlign) align; + Plugin.Config.Countdown.Align = (ConfigurationFile.TextAlign)align; Plugin.Config.Save(); } diff --git a/Plugin/Ui/SettingsTab/FloatingWindowTab.cs b/Plugin/Ui/SettingsTab/FloatingWindowTab.cs index d0aec22..1d86039 100644 --- a/Plugin/Ui/SettingsTab/FloatingWindowTab.cs +++ b/Plugin/Ui/SettingsTab/FloatingWindowTab.cs @@ -15,6 +15,7 @@ using System; using Dalamud.Interface.Components; +// using Dalamud.Interface.ImGuiFontChooserDialog; using EngageTimer.Configuration; using EngageTimer.Localization; using EngageTimer.Properties; @@ -24,6 +25,8 @@ namespace EngageTimer.Ui.SettingsTab; public static class FloatingWindowTab { + // private static SingleFontChooserDialog? _fc; + public static void Draw() { ImGui.PushTextWrapPos(); @@ -35,6 +38,7 @@ public static void Draw() Components.AutoField(Plugin.Config.FloatingWindow, "Lock"); ImGuiComponents.HelpMarker(Strings.Settings_FWTab_Lock_Help); + Components.AutoField(Plugin.Config.FloatingWindow, "HideInCutscenes"); Components.AutoField(Plugin.Config.FloatingWindow, "AutoHide"); Components.AutoField(Plugin.Config.FloatingWindow, "AutoHideTimeout", sameLine: true); @@ -45,7 +49,7 @@ public static void Draw() Components.AutoField(Plugin.Config.FloatingWindow, "EnableStopwatch"); Components.AutoField(Plugin.Config.FloatingWindow, "DecimalStopwatchPrecision", sameLine: true); - + ImGui.Separator(); if (ImGui.CollapsingHeader(Translator.TrId("Settings_FWTab_Styling"))) FwStyling(); ImGui.Separator(); @@ -81,13 +85,13 @@ private static void FwStyling() Components.AutoField(Plugin.Config.FloatingWindow, "Scale"); var configuration = Plugin.Config; - var textAlign = (int) configuration.FloatingWindow.Align; + var textAlign = (int)configuration.FloatingWindow.Align; if (ImGui.Combo(Translator.TrId("Settings_FWTab_TextAlign"), ref textAlign, Strings.Settings_FWTab_TextAlign_Left + "###Left\0" + Strings.Settings_FWTab_TextAlign_Center + "###Center\0" + Strings.Settings_FWTab_TextAlign_Right + "###Right")) { - configuration.FloatingWindow.Align = (ConfigurationFile.TextAlign) textAlign; + configuration.FloatingWindow.Align = (ConfigurationFile.TextAlign)textAlign; configuration.Save(); } @@ -96,8 +100,9 @@ private static void FwStyling() { configuration.FloatingWindow.FontSize = Math.Max(0, fontSize); configuration.Save(); + Plugin.FloatingWindowFont.UpdateFont(); - if (configuration.FloatingWindow.FontSize >= 8) Plugin.PluginInterface.UiBuilder.RebuildFonts(); + // if (configuration.FloatingWindow.FontSize >= 8) Plugin.PluginInterface.UiBuilder.RebuildFonts(); } ImGui.EndGroup(); @@ -105,8 +110,46 @@ private static void FwStyling() ImGui.BeginGroup(); Components.AutoField(Plugin.Config.FloatingWindow, "TextColor"); Components.AutoField(Plugin.Config.FloatingWindow, "BackgroundColor"); + Components.AutoField(Plugin.Config.FloatingWindow, "ForceHideWindowBorder"); ImGui.EndGroup(); + // ImGui.Text("Font:"); + // ImGui.SameLine(); + // using (Plugin.FloatingWindowFont.FontHandle?.Push()) + // { + // if (configuration.FloatingWindow.FontSpec == null) + // ImGui.Text("default"); + // else + // ImGui.Text(configuration.FloatingWindow.FontSpec.ToString()); + // } + // + // if (ImGui.Button("change font") && !_fcO) + // { + // _fc = SingleFontChooserDialog.CreateAuto((UiBuilder)Plugin.PluginInterface.UiBuilder); + // _fcO = true; + // _fc.PreviewText = "-01:23.45 6789"; + // _fc.ResultTask.ContinueWith(task => + // { + // _fcO = false; + // if (!task.IsCompleted) return; + // configuration.FloatingWindow.Font = _fc.SelectedFont; + // + // Plugin.Logger.Info("font chosen: " + _fc.SelectedFont); + // + // configuration.Save(); + // Plugin.FloatingWindowFont.UpdateFont(); + // }); + // } + // ImGui.SameLine(); + // if (ImGui.Button("reset font")) + // { + // configuration.FloatingWindow.FontSpec = null; + // configuration.Save(); + // Plugin.FloatingWindowFont.UpdateFont(); + // } + ImGui.Unindent(); } + + // private static bool _fcO = false; } \ No newline at end of file diff --git a/Plugin/Ui/SettingsTab/WebServerTab.cs b/Plugin/Ui/SettingsTab/WebServerTab.cs index cdf8ee4..ddff102 100644 --- a/Plugin/Ui/SettingsTab/WebServerTab.cs +++ b/Plugin/Ui/SettingsTab/WebServerTab.cs @@ -26,8 +26,8 @@ public static class WebServerTab public static void Draw() { ImGui.PushTextWrapPos(); - Components.Text("Settings_Web_Help"); - Components.Text("Settings_Web_HelpAdd"); + Components.Text("Settings_Web_Help".Tr()); + Components.Text("Settings_Web_HelpAdd".Tr()); ImGui.Text($"http://localhost:{Plugin.Config.WebServer.Port}/"); ImGui.SameLine(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) diff --git a/Plugin/packages.lock.json b/Plugin/packages.lock.json index 1e32153..d0ffd4f 100644 --- a/Plugin/packages.lock.json +++ b/Plugin/packages.lock.json @@ -1,12 +1,12 @@ { "version": 1, "dependencies": { - "net7.0-windows7.0": { + "net8.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[2.1.12, )", - "resolved": "2.1.12", - "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg==" + "requested": "[2.1.13, )", + "resolved": "2.1.13", + "contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ==" }, "EmbedIO": { "type": "Direct",